diff --git a/.gitignore b/.gitignore
index f226d5cc9..1a7ae0466 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,4 +76,4 @@ testem.log
# System Files
.DS_Store
Thumbs.db
-/bakery/dashboard/dashboard.zip
+/http/baker-http-dashboard/dashboard.zip
diff --git a/README.md b/README.md
index 775193ccb..9a3c593d6 100644
--- a/README.md
+++ b/README.md
@@ -74,16 +74,16 @@ Applying Baker will only be successful if you make sure that:
To get started with SBT, simply add the following to your build.sbt file:
```
-libraryDependencies += "com.ing.baker" %% "baker-recipe-dsl" % "3.0.3"
-libraryDependencies += "com.ing.baker" %% "baker-runtime" % "3.0.3"
-libraryDependencies += "com.ing.baker" %% "baker-compiler" % "3.0.3"
+libraryDependencies += "com.ing.baker" %% "baker-recipe-dsl" % version
+libraryDependencies += "com.ing.baker" %% "baker-runtime" % version
+libraryDependencies += "com.ing.baker" %% "baker-compiler" % version
```
From 1.3.x to 2.0.x we cross compile to both Scala 2.11 and 2.12.
Earlier releases are only available for Scala 2.11.
-From 3.0.x we support only Scala 2.12.
+From 3.x.x we support only Scala 2.12.
# How to contribute?
diff --git a/bakery/README.MD b/bakery/README.MD
new file mode 100644
index 000000000..ad89c5eef
--- /dev/null
+++ b/bakery/README.MD
@@ -0,0 +1,9 @@
+The `bakery` directory contains modules for setting up a bakery cluster.
+
+Bakery is a way to host a baker service using akka-runtime and expose it using API endpoints. This allows you to separate
+the running of an akka cluster and managing of the persistence from using baker for your business process.
+
+A bakery team can provide baker clusters as a service (which requires specific knowledge about akka clusters).
+A client team can focus on creating recipes based on actual business processes.
+
+The `bakery-state` module is the entry point for bakery.
diff --git a/bakery/client/src/test/java/com/ing/bakery/client/JBakerClientSpec.java b/bakery/client/src/test/java/com/ing/bakery/client/JBakerClientSpec.java
deleted file mode 100644
index c873bc18b..000000000
--- a/bakery/client/src/test/java/com/ing/bakery/client/JBakerClientSpec.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.ing.bakery.client;
-
-import com.google.common.collect.ImmutableList;
-import com.ing.baker.runtime.javadsl.Baker;
-import com.ing.bakery.javadsl.BakerClient;
-
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-
-public class JBakerClientSpec {
-
- public void shouldCompileTheRecipeWithoutIssues() throws ExecutionException, InterruptedException {
- Baker baker = BakerClient.build(
- ImmutableList.of("bakeryhost1"),
- "/api/bakery",
- ImmutableList.of(),
- "",
- ImmutableList.of(),
- Optional.empty(),
- true).get();
- }
-}
diff --git a/bakery/dashboard/src/app/home/home.component.html b/bakery/dashboard/src/app/home/home.component.html
deleted file mode 100644
index 238c26748..000000000
--- a/bakery/dashboard/src/app/home/home.component.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- Version |
- {{ bakeryVersion }} |
-
-
- State version |
- {{ stateVersion }} |
-
-
-
diff --git a/bakery/dashboard/src/assets/settings/settings.json b/bakery/dashboard/src/assets/settings/settings.json
deleted file mode 100644
index f36215181..000000000
--- a/bakery/dashboard/src/assets/settings/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "apiUrl" : "http://localhost:4200/api/bakery",
- "title" : "Bakery OSS",
- "bakeryVersion" : "123",
- "stateVersion" : "456"
-}
diff --git a/bakery/dashboard/src/proxy.conf.json b/bakery/dashboard/src/proxy.conf.json
deleted file mode 100644
index 0ae6c83ab..000000000
--- a/bakery/dashboard/src/proxy.conf.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "/api/bakery": {
- "target": "http://localhost:8080",
- "secure": false,
- "logLevel": "debug",
- "changeOrigin": true
- }
-}
diff --git a/bakery/integration-tests/src/test/resources/kubernetes/webshop-baker.yaml b/bakery/integration-tests/src/test/resources/kubernetes/webshop-baker.yaml
index f9683359e..f8b6c942a 100644
--- a/bakery/integration-tests/src/test/resources/kubernetes/webshop-baker.yaml
+++ b/bakery/integration-tests/src/test/resources/kubernetes/webshop-baker.yaml
@@ -114,7 +114,7 @@ data:
baker {
recipe-poll-interval: 5 seconds
event-sink {
- class: "com.ing.bakery.baker.KafkaEventSink",
+ class: "com.ing.bakery.components.KafkaEventSink",
bootstrap-servers: "kafka-event-sink:9092",
topic: "events"
}
diff --git a/bakery/baker-state-k8s/src/main/resources/reference.conf b/bakery/interaction-k8s-interaction-manager/src/main/resources/reference.conf
similarity index 100%
rename from bakery/baker-state-k8s/src/main/resources/reference.conf
rename to bakery/interaction-k8s-interaction-manager/src/main/resources/reference.conf
diff --git a/bakery/baker-state-k8s/src/main/scala/com/ing/bakery/baker/KubernetesInteractions.scala b/bakery/interaction-k8s-interaction-manager/src/main/scala/com/ing/bakery/baker/KubernetesInteractions.scala
similarity index 98%
rename from bakery/baker-state-k8s/src/main/scala/com/ing/bakery/baker/KubernetesInteractions.scala
rename to bakery/interaction-k8s-interaction-manager/src/main/scala/com/ing/bakery/baker/KubernetesInteractions.scala
index 4d2c48eda..8812e6e42 100644
--- a/bakery/baker-state-k8s/src/main/scala/com/ing/bakery/baker/KubernetesInteractions.scala
+++ b/bakery/interaction-k8s-interaction-manager/src/main/scala/com/ing/bakery/baker/KubernetesInteractions.scala
@@ -7,6 +7,7 @@ import akka.{Done, NotUsed}
import cats.effect.{ContextShift, IO, Resource, Timer}
import cats.implicits.catsSyntaxApplicativeError
import com.ing.baker.runtime.akka.internal.DynamicInteractionManager
+import com.ing.bakery.components.RemoteInteractionDiscovery
import com.ing.bakery.interaction.RemoteInteractionClient
import com.typesafe.config.Config
import com.typesafe.scalalogging.LazyLogging
diff --git a/bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/StateRuntimeSpec.scala b/bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/StateRuntimeSpec.scala
similarity index 97%
rename from bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/StateRuntimeSpec.scala
rename to bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/StateRuntimeSpec.scala
index e11a91ed1..88b9b2afb 100644
--- a/bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/StateRuntimeSpec.scala
+++ b/bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/StateRuntimeSpec.scala
@@ -2,6 +2,10 @@ package com.ing.bakery.baker
import akka.actor.ActorSystem
import cats.effect.{IO, Resource}
+import com.ing.baker.http.DashboardConfiguration
+import com.ing.baker.http.client.scaladsl.BakerClient
+import com.ing.baker.http.server.common.RecipeLoader
+import com.ing.baker.http.server.scaladsl.Http4sBakerServer
import com.ing.baker.il.CompiledRecipe
import com.ing.baker.runtime.akka.{AkkaBaker, AkkaBakerConfig}
import com.ing.baker.runtime.common.BakerException.NoSuchProcessException
@@ -9,12 +13,12 @@ import com.ing.baker.runtime.common.{BakerException, SensoryEventStatus}
import com.ing.baker.runtime.model.{InteractionInstance, InteractionManager}
import com.ing.baker.runtime.scaladsl.{Baker, EventInstance, InteractionInstanceInput}
import com.ing.baker.types._
-import com.ing.bakery.baker.mocks.KubeApiServer
+import com.ing.bakery.components.InteractionRegistry
import com.ing.bakery.mocks.{EventListener, RemoteInteraction}
import com.ing.bakery.recipe.Events.{ItemsReserved, OrderPlaced}
import com.ing.bakery.recipe.Ingredients.{Item, OrderId, ReservedItems}
import com.ing.bakery.recipe.{ItemReservationRecipe, SimpleRecipe, SimpleRecipe2}
-import com.ing.bakery.scaladsl.BakerClient
+import com.ing.bakery.baker.mocks.KubeApiServer
import com.ing.bakery.testing.BakeryFunSpec
import com.typesafe.config.ConfigFactory
import org.mockserver.integration.ClientAndServer
@@ -474,7 +478,13 @@ class StateRuntimeSpec extends BakeryFunSpec with Matchers {
_ <- Resource.eval(eventListener.eventSink.attach(baker))
_ <- Resource.eval(RecipeLoader.loadRecipesIntoBaker(getResourceDirectoryPathSafe, baker))
- server <- BakerService.resource(baker, executionContext, InetSocketAddress.createUnresolved("127.0.0.1", 0), "/api/bakery", "/opt/docker/dashboard", loggingEnabled = true)
+ server <- Http4sBakerServer.resource(
+ baker,
+ executionContext,
+ InetSocketAddress.createUnresolved("127.0.0.1", 0),
+ apiUrlPrefix = "/api/bakery",
+ dashboardConfiguration = DashboardConfiguration(enabled = true, applicationName = "StateRuntimeSpec", clusterInformation = Map.empty),
+ loggingEnabled = true)
client <- BakerClient.resource(server.baseUri, "/api/bakery", executionContext)
} yield Context(
diff --git a/bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/TestInteractionRegistry.scala b/bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/TestInteractionRegistry.scala
similarity index 96%
rename from bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/TestInteractionRegistry.scala
rename to bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/TestInteractionRegistry.scala
index 14e5350f7..d5dc6cc90 100644
--- a/bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/TestInteractionRegistry.scala
+++ b/bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/TestInteractionRegistry.scala
@@ -4,6 +4,7 @@ import akka.actor.ActorSystem
import cats.effect.{IO, Resource}
import com.ing.baker.runtime.model.InteractionManager
import com.ing.bakery.baker.mocks.KubeApiServer
+import com.ing.bakery.components.{BaseInteractionRegistry, LocalhostInteractions}
import com.ing.bakery.interaction.BaseRemoteInteractionClient
import com.ing.bakery.mocks.RemoteInteraction
import com.typesafe.config.{Config, ConfigValueFactory}
diff --git a/bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/mocks/KubeApiServer.scala b/bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/mocks/KubeApiServer.scala
similarity index 100%
rename from bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/mocks/KubeApiServer.scala
rename to bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/mocks/KubeApiServer.scala
diff --git a/bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/mocks/WatchEvent.scala b/bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/mocks/WatchEvent.scala
similarity index 100%
rename from bakery/baker-state-k8s/src/test/scala/com/ing/bakery/baker/mocks/WatchEvent.scala
rename to bakery/interaction-k8s-interaction-manager/src/test/scala/com/ing/bakery/baker/mocks/WatchEvent.scala
diff --git a/bakery/state/src/it/resources/application.conf b/bakery/state/src/it/resources/application.conf
index d289272bd..94abfc03a 100644
--- a/bakery/state/src/it/resources/application.conf
+++ b/bakery/state/src/it/resources/application.conf
@@ -43,10 +43,9 @@ baker {
localhost.ports = [ 8081 ]
}
event-sink {
- class: ""
+ class = ""
}
api-logging-enabled = false
- dashboard-path = "/Users/hr29bv/work/baker/bakery/dashboard/dist"
}
inmemory-read-journal {
diff --git a/bakery/state/src/main/resources/reference.conf b/bakery/state/src/main/resources/reference.conf
index a33e44f0a..ae6d61c35 100644
--- a/bakery/state/src/main/resources/reference.conf
+++ b/bakery/state/src/main/resources/reference.conf
@@ -9,9 +9,9 @@ baker {
encryption.enabled = off
metrics-port = 9095
+ api-host = "0.0.0.0"
api-port = 8080
api-url-prefix = "/api/bakery"
- dashboard-path = "/opt/docker/dashboard"
api-logging-enabled = false
bake-timeout = 30 seconds
@@ -29,7 +29,7 @@ baker {
}
interactions {
- class = "com.ing.bakery.baker.BaseInteractionRegistry"
+ class = "com.ing.bakery.components.BaseInteractionRegistry"
localhost {
port = 8081
api-url-prefix = "/api/bakery/interactions"
@@ -155,10 +155,10 @@ akka {
liveness-path = "health/alive"
liveness-checks {
cluster-health = "akka.sensors.ClusterHealthCheck"
- name = "com.ing.bakery.baker.WatcherReadinessCheck"
+ name = "com.ing.bakery.components.WatcherReadinessCheck"
}
readiness-checks {
- name = "com.ing.bakery.baker.BakerReadinessCheck"
+ name = "com.ing.bakery.components.BakerReadinessCheck"
}
}
}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/Bakery.scala b/bakery/state/src/main/scala/com/ing/bakery/Bakery.scala
similarity index 81%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/Bakery.scala
rename to bakery/state/src/main/scala/com/ing/bakery/Bakery.scala
index 6b37bfa99..6e64dae83 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/Bakery.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/Bakery.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery
import akka.actor.ActorSystem
import akka.cluster.Cluster
@@ -7,6 +7,7 @@ import com.ing.baker.runtime.akka.{AkkaBaker, AkkaBakerConfig}
import com.ing.baker.runtime.model.InteractionManager
import com.ing.baker.runtime.recipe_manager.{ActorBasedRecipeManager, RecipeManager}
import com.ing.baker.runtime.scaladsl.Baker
+import com.ing.bakery.components.{Cassandra, EventSink, InteractionRegistry, Watcher}
import com.typesafe.config.{Config, ConfigFactory}
import com.typesafe.scalalogging.LazyLogging
import io.prometheus.client.CollectorRegistry
@@ -15,16 +16,16 @@ import org.http4s.metrics.prometheus.Prometheus
import java.io.File
import scala.concurrent.ExecutionContext
-case class Bakery(baker: Baker,
- executionContext: ExecutionContext,
- system: ActorSystem)
+case class AkkaBakery(baker: Baker, system: ActorSystem) {
+ def executionContext: ExecutionContext = system.dispatcher
+}
object Bakery extends LazyLogging {
- def resource(optionalConfig: Option[Config],
- externalContext: Option[Any] = None,
- interactionManager: Option[InteractionManager[IO]] = None,
- recipeManager: Option[RecipeManager] = None) : Resource[IO, Bakery] = {
+ def akkaBakery(optionalConfig: Option[Config],
+ externalContext: Option[Any] = None,
+ interactionManager: Option[InteractionManager[IO]] = None,
+ recipeManager: Option[RecipeManager] = None) : Resource[IO, AkkaBakery] = {
val configPath = sys.env.getOrElse("CONFIG_DIRECTORY", "/opt/docker/conf")
val config = optionalConfig.getOrElse(ConfigFactory.load(ConfigFactory.parseFile(new File(s"$configPath/application.conf"))))
val bakerConfig = config.getConfig("baker")
@@ -55,9 +56,7 @@ object Bakery extends LazyLogging {
bakerActorProvider = AkkaBakerConfig.bakerProviderFrom(config),
timeouts = AkkaBakerConfig.Timeouts.apply(config),
bakerValidationSettings = AkkaBakerConfig.BakerValidationSettings.from(config))(system))
- _ <- Resource.make(IO {
- baker
- })(baker => IO.fromFuture(IO(baker.gracefulShutdown())))
+ _ <- Resource.make(IO {baker})(baker => IO.fromFuture(IO(baker.gracefulShutdown())))
_ <- Resource.eval(eventSink.attach(baker))
_ <- Resource.eval(IO.async[Unit] { callback =>
//If using local Baker the registerOnMemberUp is never called, should onl be used during local testing.
@@ -70,7 +69,7 @@ object Bakery extends LazyLogging {
}
})
- } yield Bakery(baker, system.dispatcher, system)
+ } yield AkkaBakery(baker, system)
}
}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/ClosableBakery.scala b/bakery/state/src/main/scala/com/ing/bakery/ClosableBakery.scala
similarity index 62%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/ClosableBakery.scala
rename to bakery/state/src/main/scala/com/ing/bakery/ClosableBakery.scala
index 77c926f4a..be1e666b1 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/ClosableBakery.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/ClosableBakery.scala
@@ -1,20 +1,18 @@
-package com.ing.bakery.baker
+package com.ing.bakery
-import java.io.Closeable
import akka.actor.ActorSystem
import cats.effect.IO
import com.ing.baker.runtime.model.InteractionManager
import com.ing.baker.runtime.recipe_manager.RecipeManager
import com.ing.baker.runtime.scaladsl.Baker
-import com.ing.bakery.baker.Bakery.resource
+import com.ing.bakery.Bakery.akkaBakery
import com.typesafe.config.Config
-import scala.concurrent.ExecutionContext
+import java.io.Closeable
class ClosableBakery(baker: Baker,
- executionContext: ExecutionContext,
system: ActorSystem,
- close: IO[Unit]) extends Bakery(baker, executionContext, system) with Closeable {
+ close: IO[Unit]) extends AkkaBakery(baker, system) with Closeable {
override def close(): Unit = close.unsafeRunSync()
}
@@ -22,13 +20,12 @@ object ClosableBakery {
/**
* Create bakery instance as external context
* @param externalContext optional external context in which Bakery is running, e.g. Spring context
- * @return
*/
def instance(optionalConfig: Option[Config],
externalContext: Option[Any],
interactionManager: Option[InteractionManager[IO]] = None,
recipeManager: Option[RecipeManager] = None): ClosableBakery = {
- val (baker: Bakery, close: IO[Unit]) = resource(optionalConfig, externalContext, interactionManager, recipeManager).allocated.unsafeRunSync()
- new ClosableBakery(baker.baker, baker.executionContext, baker.system, close)
+ val (baker: AkkaBakery, close: IO[Unit]) = akkaBakery(optionalConfig, externalContext, interactionManager, recipeManager).allocated.unsafeRunSync()
+ new ClosableBakery(baker.baker, baker.system, close)
}
-}
\ No newline at end of file
+}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/Main.scala b/bakery/state/src/main/scala/com/ing/bakery/Main.scala
similarity index 59%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/Main.scala
rename to bakery/state/src/main/scala/com/ing/bakery/Main.scala
index 2b8f346f8..2e5987524 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/Main.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/Main.scala
@@ -1,13 +1,16 @@
-package com.ing.bakery.baker
-
-import java.io.File
-import java.net.InetSocketAddress
+package com.ing.bakery
import cats.effect.{ExitCode, IO, IOApp}
+import com.ing.baker.http.DashboardConfiguration
+import com.ing.baker.http.server.common.RecipeLoader
+import com.ing.baker.http.server.scaladsl.{Http4sBakerServer, Http4sBakerServerConfiguration}
+import com.ing.bakery.components.BakerReadinessCheck
import com.ing.bakery.metrics.MetricService
import com.typesafe.config.ConfigFactory
import com.typesafe.scalalogging.LazyLogging
+import java.io.File
+import java.net.InetSocketAddress
import scala.concurrent.duration.Duration
object Main extends IOApp with LazyLogging {
@@ -16,31 +19,26 @@ object Main extends IOApp with LazyLogging {
val configPath = sys.env.getOrElse("CONFIG_DIRECTORY", "/opt/docker/conf")
val config = ConfigFactory.load(ConfigFactory.parseFile(new File(s"$configPath/application.conf")))
- val bakerConfig = config.getConfig("baker")
- val apiPort = bakerConfig.getInt("api-port")
- val metricsPort = bakerConfig.getInt("metrics-port")
- val apiUrlPrefix = bakerConfig.getString("api-url-prefix")
- val dashboardPath = bakerConfig.getString("dashboard-path")
- val loggingEnabled = bakerConfig.getBoolean("api-logging-enabled")
+ val metricsPort = config.getInt("baker.metrics-port")
(for {
- bakery <- Bakery.resource(Some(config))
+ bakery <- Bakery.akkaBakery(Some(config))
_ <- MetricService.resource(InetSocketAddress.createUnresolved("0.0.0.0", metricsPort), bakery.executionContext)
- bakerService <- BakerService.resource(
+ bakerService <- Http4sBakerServer.resource(
bakery.baker,
- bakery.executionContext,
- InetSocketAddress.createUnresolved("0.0.0.0", apiPort),
- apiUrlPrefix, dashboardPath, loggingEnabled)
+ Http4sBakerServerConfiguration.fromConfig(config),
+ DashboardConfiguration.fromConfig(config),
+ bakery.executionContext)
} yield (bakery, bakerService))
.use { case (bakery, bakerService) =>
logger.info(s"Bakery started at ${bakerService.address}/${bakerService.baseUri}, enabling the readiness in Akka management")
BakerReadinessCheck.enable()
- RecipeLoader.pollRecipesUpdates(configPath, bakery,
+ RecipeLoader.pollRecipesUpdates(configPath, bakery.baker,
Duration.fromNanos(config.getDuration("baker.recipe-poll-interval").toNanos))
- }.as(ExitCode.Success)
+ }.as(ExitCode.Success)
}
}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/BakerService.scala b/bakery/state/src/main/scala/com/ing/bakery/baker/BakerService.scala
deleted file mode 100644
index d8b65560a..000000000
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/BakerService.scala
+++ /dev/null
@@ -1,203 +0,0 @@
-package com.ing.bakery.baker
-
-import cats.data.OptionT
-import cats.effect.{Blocker, ContextShift, IO, Resource, Sync, Timer}
-import cats.implicits._
-import com.ing.baker.runtime.common.BakerException
-import com.ing.baker.runtime.scaladsl.{Baker, BakerResult, EncodedRecipe, EventInstance, InteractionExecutionResult}
-import com.ing.baker.runtime.serialization.InteractionExecution
-import com.ing.baker.runtime.serialization.InteractionExecutionJsonCodecs._
-import com.ing.baker.runtime.serialization.JsonDecoders._
-import com.ing.baker.runtime.serialization.JsonEncoders._
-import com.typesafe.scalalogging.LazyLogging
-import io.circe._
-import io.circe.generic.auto._
-import io.prometheus.client.CollectorRegistry
-import org.http4s._
-import org.http4s.circe._
-import org.http4s.dsl.io._
-import org.http4s.implicits._
-import org.http4s.metrics.prometheus.Prometheus
-import org.http4s.server.blaze.BlazeServerBuilder
-import org.http4s.server.middleware.{CORS, Logger, Metrics}
-import org.http4s.server.{Router, Server}
-import org.slf4j.LoggerFactory
-
-import java.io.File
-import java.net.InetSocketAddress
-import java.nio.charset.Charset
-import scala.concurrent.duration.DurationInt
-import scala.concurrent.{ExecutionContext, Future}
-
-object BakerService {
-
- def resource(baker: Baker, ec: ExecutionContext, hostname: InetSocketAddress, apiUrlPrefix: String, dashboardPath: String, loggingEnabled: Boolean)
- (implicit sync: Sync[IO], cs: ContextShift[IO], timer: Timer[IO]): Resource[IO, Server[IO]] = {
-
- val bakeryRequestClassifier: Request[IO] => Option[String] = { request =>
- val uriPath = request.uri.path
- val p = uriPath.takeRight(uriPath.length - apiUrlPrefix.length)
-
- if (p.startsWith("/app")) Some(p) // cardinality is low, we don't care
- else if (p.startsWith("/instances")) {
- val action = p.split('/') // /instances///... - we don't want ID here
- if (action.length >= 4) Some(s"/instances/${action(3)}") else Some("/instances/state")
- } else None
- }
-
- val apiLoggingAction: Option[String => IO[Unit]] = if (loggingEnabled) {
- val apiLogger = LoggerFactory.getLogger("API")
- Some(s => IO(apiLogger.info(s)))
- } else None
-
- for {
- metrics <- Prometheus.metricsOps[IO](CollectorRegistry.defaultRegistry, "http_api")
- blocker <- Blocker[IO]
- server <- BlazeServerBuilder[IO](ec)
- .bindSocketAddress(hostname)
- .withHttpApp(
- CORS.policy
- .withAllowOriginAll
- .withAllowCredentials(true)
- .withMaxAge(1.day)(
- Logger.httpApp(
- logHeaders = loggingEnabled,
- logBody = loggingEnabled,
- logAction = apiLoggingAction) {
-
- def dashboardFile(request: Request[IO], filename: String): OptionT[IO, Response[IO]] =
- StaticFile.fromFile(new File(dashboardPath + "/" + filename), blocker, Some(request))
-
- def index(request: Request[IO]) = dashboardFile(request, "index.html").getOrElseF(NotFound())
-
- Router(
- apiUrlPrefix -> Metrics[IO](metrics, classifierF = bakeryRequestClassifier)(routes(baker)),
- "/" -> HttpRoutes.of[IO] {
- case request =>
- if (dashboardPath.isEmpty) NotFound()
- else dashboardFile(request, request.pathInfo).getOrElseF(index(request))
- }
- ) orNotFound
- })).resource
- } yield server
- }
-
- def routes(baker: Baker)(implicit cs: ContextShift[IO], timer: Timer[IO]): HttpRoutes[IO] =
- new BakerService(baker).routes
-}
-
-final class BakerService private(baker: Baker)(implicit cs: ContextShift[IO], timer: Timer[IO]) extends LazyLogging {
-
- object CorrelationId extends OptionalQueryParamDecoderMatcher[String]("correlationId")
-
- private class RegExpValidator(regexp: String) {
- def unapply(str: String): Option[String] = if (str.matches(regexp)) Some(str) else None
- }
-
- private object RecipeId extends RegExpValidator("[A-Za-z0-9]+")
-
- private object RecipeInstanceId extends RegExpValidator("[A-Za-z0-9-]+")
-
- private object InteractionName extends RegExpValidator("[A-Za-z0-9_]+")
-
- implicit val recipeDecoder: EntityDecoder[IO, EncodedRecipe] = jsonOf[IO, EncodedRecipe]
-
- implicit val eventInstanceDecoder: EntityDecoder[IO, EventInstance] = jsonOf[IO, EventInstance]
- implicit val interactionExecutionRequestDecoder: EntityDecoder[IO, InteractionExecution.ExecutionRequest] = jsonOf[IO, InteractionExecution.ExecutionRequest]
- implicit val bakerResultEntityEncoder: EntityEncoder[IO, BakerResult] = jsonEncoderOf[IO, BakerResult]
-
- def routes: HttpRoutes[IO] = app <+> instance
-
- private def callBaker[A](f: => Future[A])(implicit encoder: Encoder[A]): IO[Response[IO]] =
- callBakerIO(IO.fromFuture(IO(f)))
-
- private def callBakerIO[A](io: => IO[A])(implicit encoder: Encoder[A]): IO[Response[IO]] =
- io.attempt.flatMap {
- case Left(e: BakerException) => Ok(BakerResult(e))
- case Left(e) =>
- logger.error(s"Unexpected exception happened when calling Baker", e)
- InternalServerError(s"No other exception but BakerExceptions should be thrown here: ${e.getCause}")
- case Right(()) => Ok(BakerResult.Ack)
- case Right(a) => Ok(BakerResult(a))
- }
-
- private def app: HttpRoutes[IO] = Router("/app" ->
- HttpRoutes.of[IO] {
- case GET -> Root / "health" => Ok()
-
- case GET -> Root / "interactions" => callBaker(baker.getAllInteractions)
-
- case GET -> Root / "interactions" / InteractionName(name) => callBaker(baker.getInteraction(name))
-
- case req@POST -> Root / "interactions" / "execute" =>
- for {
- executionRequest <- req.as[InteractionExecution.ExecutionRequest]
- result <- callBakerIO(
- IO.fromFuture(IO(baker.executeSingleInteraction(executionRequest.id, executionRequest.ingredients)))
- .map(_.toSerializationInteractionExecutionResult))
- } yield result
-
- case req@POST -> Root / "recipes" =>
- for {
- encodedRecipe <- req.as[EncodedRecipe]
- recipe <- RecipeLoader.fromBytes(encodedRecipe.base64.getBytes(Charset.forName("UTF-8")))
- result <- callBaker(baker.addRecipe(recipe, validate = true))
- } yield result
-
- case GET -> Root / "recipes" => callBaker(baker.getAllRecipes)
-
- case GET -> Root / "recipes" / RecipeId(recipeId) => callBaker(baker.getRecipe(recipeId))
-
- case GET -> Root / "recipes" / RecipeId(recipeId) / "visual" => callBaker(baker.getRecipeVisual(recipeId))
- })
-
- private def instance: HttpRoutes[IO] = Router("/instances" -> HttpRoutes.of[IO] {
-
- case GET -> Root => callBaker(baker.getAllRecipeInstancesMetadata)
-
- case GET -> Root / RecipeInstanceId(recipeInstanceId) => callBaker(baker.getRecipeInstanceState(recipeInstanceId))
-
- case GET -> Root / RecipeInstanceId(recipeInstanceId) / "events" => callBaker(baker.getEvents(recipeInstanceId))
-
- case GET -> Root / RecipeInstanceId(recipeInstanceId) / "ingredients" => callBaker(baker.getIngredients(recipeInstanceId))
-
- case GET -> Root / RecipeInstanceId(recipeInstanceId) / "visual" => callBaker(baker.getVisualState(recipeInstanceId))
-
- case POST -> Root / RecipeInstanceId(recipeInstanceId) / "bake" / RecipeId(recipeId) => callBaker(baker.bake(recipeId, recipeInstanceId))
-
- case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "fire-and-resolve-when-received" :? CorrelationId(maybeCorrelationId) =>
- for {
- event <- req.as[EventInstance]
- result <- callBaker(baker.fireEventAndResolveWhenReceived(recipeInstanceId, event, maybeCorrelationId))
- } yield result
-
- case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "fire-and-resolve-when-completed" :? CorrelationId(maybeCorrelationId) =>
- for {
- event <- req.as[EventInstance]
- result <- callBaker(baker.fireEventAndResolveWhenCompleted(recipeInstanceId, event, maybeCorrelationId))
- } yield result
-
- case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "fire-and-resolve-on-event" / onEvent :? CorrelationId(maybeCorrelationId) =>
- for {
- event <- req.as[EventInstance]
- result <- callBaker(baker.fireEventAndResolveOnEvent(recipeInstanceId, event, onEvent, maybeCorrelationId))
- } yield result
-
- case POST -> Root / RecipeInstanceId(recipeInstanceId) / "interaction" / InteractionName(interactionName) / "retry" =>
- for {
- result <- callBaker(baker.retryInteraction(recipeInstanceId, interactionName))
- } yield result
-
- case POST -> Root / RecipeInstanceId(recipeInstanceId) / "interaction" / InteractionName(interactionName) / "stop-retrying" =>
- for {
- result <- callBaker(baker.stopRetryingInteraction(recipeInstanceId, interactionName))
- } yield result
-
- case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "interaction" / InteractionName(interactionName) / "resolve" =>
- for {
- event <- req.as[EventInstance]
- result <- callBaker(baker.resolveInteraction(recipeInstanceId, interactionName, event))
- } yield result
- })
-
-}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/BakeryExecutorJava.scala b/bakery/state/src/main/scala/com/ing/bakery/baker/BakeryExecutorJava.scala
deleted file mode 100644
index 049c6b7f2..000000000
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/BakeryExecutorJava.scala
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.ing.bakery.baker
-
-import com.ing.baker.runtime.common.BakerException
-import com.ing.baker.runtime.scaladsl.{BakerResult, EncodedRecipe, EventInstance}
-import com.ing.baker.runtime.serialization.JsonDecoders._
-import com.ing.baker.runtime.serialization.JsonEncoders._
-import com.typesafe.scalalogging.LazyLogging
-import io.circe.Encoder
-import io.circe.generic.auto._
-import io.circe.parser.parse
-
-import java.nio.charset.Charset
-import java.util.{Optional, UUID}
-import java.util.concurrent.{CompletableFuture => JFuture}
-import scala.compat.java8.FutureConverters.FutureOps
-import scala.concurrent.{ExecutionContext, Future}
-
-class BakeryExecutorJava(bakery: Bakery) extends LazyLogging {
-
- implicit val executionContext: ExecutionContext = bakery.executionContext
-
- private def callBaker[A](f: => Future[A])(implicit encoder: Encoder[A]): Future[String] = {
- f.map {
- case () => BakerResult.Ack
- case a => BakerResult(a)
- }.recover {
- case e: BakerException => BakerResult(e)
- case e: Throwable =>
- val errorId = UUID.randomUUID().toString
- logger.error(s"Unexpected exception happened when calling Baker (id='$errorId').", e)
- BakerResult(BakerException.UnexpectedException(errorId))
- }.map(bakerResultEncoder.apply(_).noSpaces)
- }
-
- private def callBakerJava[A](f: => Future[A])(implicit encoder: Encoder[A]): JFuture[String] = {
- callBaker(f)(encoder).toJava.toCompletableFuture
- }
-
- def appGetAllInteractions: JFuture[String] = callBakerJava(bakery.baker.getAllInteractions)
-
- def appGetInteraction(interactionName: String): JFuture[String] = callBakerJava(bakery.baker.getInteraction(interactionName))
-
- def appAddRecipe(recipe: String): JFuture[String] = {
- (for {
- json <- parse(recipe).toOption
- encodedRecipe <- json.as[EncodedRecipe].toOption
- } yield RecipeLoader.fromBytes(encodedRecipe.base64.getBytes(Charset.forName("UTF-8"))).unsafeToFuture())
- .map(_.flatMap(recipe => callBaker(bakery.baker.addRecipe(recipe, validate = false))))
- .getOrElse(Future.failed(new IllegalStateException("Error adding recipe")))
- }.toJava.toCompletableFuture
-
- def appGetRecipe(recipeId: String): JFuture[String] = callBakerJava(bakery.baker.getRecipe(recipeId))
-
- def appGetAllRecipes: JFuture[String] = callBakerJava(bakery.baker.getAllRecipes)
-
- def appGetVisualRecipe(recipeId: String): JFuture[String] = callBakerJava(bakery.baker.getRecipeVisual(recipeId))
-
- def instanceGet(recipeInstanceId: String): JFuture[String] = callBakerJava(bakery.baker.getRecipeInstanceState(recipeInstanceId))
-
- def instanceGetEvents(recipeInstanceId: String): JFuture[String] = callBakerJava(bakery.baker.getEvents(recipeInstanceId))
-
- def instanceGetIngredients(recipeInstanceId: String): JFuture[String] = callBakerJava(bakery.baker.getIngredients(recipeInstanceId))
-
- def instanceGetVisual(recipeInstanceId: String): JFuture[String] = callBakerJava(bakery.baker.getVisualState(recipeInstanceId))
-
- def instanceBake(recipeId: String, recipeInstanceId: String): JFuture[String] = callBakerJava(bakery.baker.bake(recipeId, recipeInstanceId))
-
- private def toOption[T](opt: Optional[T]): Option[T] = if (opt.isPresent) Some(opt.get()) else None
-
- private def parseEventAndExecute[A](eventJson: String, f: EventInstance => Future[A])(implicit encoder: Encoder[A]): JFuture[String] = (for {
- json <- parse(eventJson)
- eventInstance <- json.as[EventInstance]
- } yield {
- callBaker(f(eventInstance))
- }).getOrElse(Future.failed(new IllegalArgumentException("Can't process event"))).toJava.toCompletableFuture
-
- def instanceFireAndResolveWhenReceived(recipeInstanceId: String, eventJson: String, maybeCorrelationId: Optional[String]): JFuture[String] =
- parseEventAndExecute(eventJson, bakery.baker.fireEventAndResolveWhenReceived(recipeInstanceId, _, toOption(maybeCorrelationId)))
-
- def instanceFireAndResolveWhenCompleted(recipeInstanceId: String, eventJson: String, maybeCorrelationId: Optional[String]): JFuture[String] =
- parseEventAndExecute(eventJson, bakery.baker.fireEventAndResolveWhenCompleted(recipeInstanceId, _, toOption(maybeCorrelationId)))
-
- def instancefireAndResolveOnEvent(recipeInstanceId: String, eventJson: String, event: String, maybeCorrelationId: Optional[String]): JFuture[String] =
- parseEventAndExecute(eventJson, bakery.baker.fireEventAndResolveOnEvent(recipeInstanceId, _, event, toOption(maybeCorrelationId)))
-
- def instanceInteractionRetry(recipeInstanceId: String, interactionName: String): JFuture[String] =
- callBakerJava(bakery.baker.retryInteraction(recipeInstanceId, interactionName))
-
- def instanceInteractionStopRetrying(recipeInstanceId: String, interactionName: String): JFuture[String] =
- callBakerJava(bakery.baker.stopRetryingInteraction(recipeInstanceId, interactionName))
-
- def instanceInteractionResolve(recipeInstanceId: String, interactionName: String, eventJson: String): JFuture[String] =
- parseEventAndExecute(eventJson, bakery.baker.resolveInteraction(recipeInstanceId, interactionName, _))
-
-}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/BakerReadinessCheck.scala b/bakery/state/src/main/scala/com/ing/bakery/components/BakerReadinessCheck.scala
similarity index 89%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/BakerReadinessCheck.scala
rename to bakery/state/src/main/scala/com/ing/bakery/components/BakerReadinessCheck.scala
index e9afe20e5..31b9be10b 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/BakerReadinessCheck.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/components/BakerReadinessCheck.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery.components
import scala.concurrent.Future
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/Cassandra.scala b/bakery/state/src/main/scala/com/ing/bakery/components/Cassandra.scala
similarity index 97%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/Cassandra.scala
rename to bakery/state/src/main/scala/com/ing/bakery/components/Cassandra.scala
index 86b9b6762..2b982982a 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/Cassandra.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/components/Cassandra.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery.components
import akka.actor.ActorSystem
import cats.effect.{Async, ContextShift, IO, Resource, Timer}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/EventSink.scala b/bakery/state/src/main/scala/com/ing/bakery/components/EventSink.scala
similarity index 99%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/EventSink.scala
rename to bakery/state/src/main/scala/com/ing/bakery/components/EventSink.scala
index 16d3b2e3a..6e1790372 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/EventSink.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/components/EventSink.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery.components
import cats.effect.{ContextShift, IO, Resource, Timer}
import com.ing.baker.runtime.akka.AkkaBaker
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/InteractionRegistry.scala b/bakery/state/src/main/scala/com/ing/bakery/components/InteractionRegistry.scala
similarity index 97%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/InteractionRegistry.scala
rename to bakery/state/src/main/scala/com/ing/bakery/components/InteractionRegistry.scala
index 6b235f4b8..332872d57 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/InteractionRegistry.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/components/InteractionRegistry.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery.components
import akka.actor.ActorSystem
import cats.Traverse
@@ -134,7 +134,7 @@ trait RemoteInteractionDiscovery extends LazyLogging {
case _: IOException =>
logger.info(s"Can't connect to interactions @ ${uri.toString}, the container may still be starting...")
case _ =>
- logger.error(s"Failed to list interactions @ ${uri.toString}", e)
+ logger.warn(s"Failed to list interactions @ ${uri.toString}. The list of available interactions will not be updated.", e)
}
IO.sleep(times) *> attempt(count - 1, times)
case Right(a) => IO(a)
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/LocalhostInteractions.scala b/bakery/state/src/main/scala/com/ing/bakery/components/LocalhostInteractions.scala
similarity index 97%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/LocalhostInteractions.scala
rename to bakery/state/src/main/scala/com/ing/bakery/components/LocalhostInteractions.scala
index ec3a7886f..ea089afa5 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/LocalhostInteractions.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/components/LocalhostInteractions.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery.components
import akka.actor.ActorSystem
import cats.effect.{ContextShift, IO, Resource, Timer}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/Watcher.scala b/bakery/state/src/main/scala/com/ing/bakery/components/Watcher.scala
similarity index 97%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/Watcher.scala
rename to bakery/state/src/main/scala/com/ing/bakery/components/Watcher.scala
index 51a8ed921..902125f0a 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/Watcher.scala
+++ b/bakery/state/src/main/scala/com/ing/bakery/components/Watcher.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.bakery.components
import akka.actor.ActorSystem
import cats.effect.{ContextShift, IO, Resource, Timer}
diff --git a/bakery/state/src/test/scala/com/ing/bakery/baker/TestWatcher.scala b/bakery/state/src/test/scala/com/ing/bakery/baker/TestWatcher.scala
index 89757f70a..37b70e835 100644
--- a/bakery/state/src/test/scala/com/ing/bakery/baker/TestWatcher.scala
+++ b/bakery/state/src/test/scala/com/ing/bakery/baker/TestWatcher.scala
@@ -3,6 +3,7 @@ import akka.actor.ActorSystem
import cats.effect.{IO, Resource, Timer}
import com.typesafe.config.Config
import cats.implicits._
+import com.ing.bakery.components.{Cassandra, Watcher}
import scala.concurrent.duration._
object TestWatcher {
diff --git a/bakery/state/src/test/scala/com/ing/bakery/baker/WatcherSpec.scala b/bakery/state/src/test/scala/com/ing/bakery/baker/WatcherSpec.scala
index a88890afb..2e5512925 100644
--- a/bakery/state/src/test/scala/com/ing/bakery/baker/WatcherSpec.scala
+++ b/bakery/state/src/test/scala/com/ing/bakery/baker/WatcherSpec.scala
@@ -4,6 +4,7 @@ import akka.actor.ActorSystem
import cats.effect.{ContextShift, IO, Resource, Timer}
import com.ing.baker.runtime.akka.internal.CachingInteractionManager
import com.ing.baker.runtime.akka.{AkkaBaker, AkkaBakerConfig}
+import com.ing.bakery.components.{Watcher, WatcherReadinessCheck}
import com.ing.bakery.mocks.EventListener
import com.ing.bakery.testing.BakeryFunSpec
import com.typesafe.config.ConfigFactory
diff --git a/bakery/state/src/test/scala/com/ing/bakery/mocks/EventListener.scala b/bakery/state/src/test/scala/com/ing/bakery/mocks/EventListener.scala
index 817b243cb..c31122bbe 100644
--- a/bakery/state/src/test/scala/com/ing/bakery/mocks/EventListener.scala
+++ b/bakery/state/src/test/scala/com/ing/bakery/mocks/EventListener.scala
@@ -2,7 +2,7 @@ package com.ing.bakery.mocks
import cats.effect.{ContextShift, IO}
import com.ing.baker.runtime.common.{BakerEvent, EventInstance}
-import com.ing.bakery.baker.EventSink
+import com.ing.bakery.components.EventSink
import scala.collection.mutable
diff --git a/build.sbt b/build.sbt
index 8b81e5a18..c1c46c16c 100644
--- a/build.sbt
+++ b/build.sbt
@@ -53,7 +53,7 @@ val dockerSettings: Seq[Setting[_]] = Seq(
Docker / maintainer := "The Bakery Team",
dockerBaseImage := "adoptopenjdk/openjdk11",
dockerUpdateLatest := true, // todo only for master branch
- Docker / version := "local", // used by smoke tests for locally built images
+ Docker / version := "local" // used by smoke tests for locally built images
)
val dependencyOverrideSettings: Seq[Setting[_]] = Seq(
@@ -78,13 +78,12 @@ val dependencyOverrideSettings: Seq[Setting[_]] = Seq(
jawnParser,
nettyHandler,
bouncyCastleBcprov,
- bouncyCastleBcpkix,
+ bouncyCastleBcpkix
)
)
lazy val noPublishSettings: Seq[Setting[_]] = Seq(
publish := {},
- publishLocal := {},
publishArtifact := false
)
@@ -242,7 +241,7 @@ lazy val `baker-recipe-dsl`: Project = project.in(file("core/recipe-dsl"))
compileDeps(
javaxInject,
paranamer,
- scalaReflect(scalaVersion.value),
+ scalaReflect(scalaVersion.value)
) ++
testDeps(
scalaTest,
@@ -263,28 +262,10 @@ lazy val `baker-recipe-compiler`: Project = project.in(file("core/recipe-compile
.dependsOn(`baker-recipe-dsl`, `baker-intermediate-language`, testScope(`baker-recipe-dsl`))
-lazy val `bakery-interaction-protocol`: Project = project.in(file("bakery/interaction-protocol"))
- .settings(defaultModuleSettings)
- .settings(scalaPBSettings)
- .settings(
- moduleName := "bakery-interaction-protocol",
- libraryDependencies ++= Seq(
- http4s,
- http4sDsl,
- http4sServer,
- http4sClient,
- http4sCirce,
- http4sPrometheus,
- prometheus,
- prometheusJmx
- )
- )
- .dependsOn(`baker-interface`)
-
-lazy val `bakery-client`: Project = project.in(file("bakery/client"))
+lazy val `baker-http-client`: Project = project.in(file("http/baker-http-client"))
.settings(defaultModuleSettings)
.settings(
- moduleName := "bakery-client",
+ moduleName := "baker-http-client",
libraryDependencies ++= Seq(
http4s,
http4sDsl,
@@ -302,38 +283,131 @@ lazy val `bakery-client`: Project = project.in(file("bakery/client"))
)
.dependsOn(`baker-interface`)
-val npmBuildTask = taskKey[File]("Dashboard build")
-lazy val `bakery-dashboard`: Project = project.in(file("bakery/dashboard"))
+lazy val `baker-http-server`: Project = project.in(file("http/baker-http-server"))
+ .settings(defaultModuleSettings)
+ .settings(yPartialUnificationSetting)
+ .settings(
+ moduleName := "baker-http-server",
+ libraryDependencies ++= Seq(
+ slf4jApi,
+ logback,
+ http4s,
+ http4sDsl,
+ http4sCirce,
+ http4sServer,
+ http4sPrometheus,
+ prometheus,
+ prometheusJmx
+ ) ++ testDeps(mockitoScala, mockitoScalaTest, catsEffectTesting)
+ )
+ .dependsOn(
+ `baker-interface`,
+ `baker-http-dashboard`,
+ testScope(`baker-recipe-compiler`)
+ )
+
+val npmInputFiles = taskKey[Set[File]]("List of files which are used by the npmBuildTask. Used to determine if something has changed and an npm build needs to be redone.")
+val npmBuildTask = taskKey[File]("Uses NPM to build the dashboard into the dist directory")
+val zipDistDirectory = taskKey[File]("Creates a zip file of the dashboard files")
+val staticDashboardFilePrefix = settingKey[String]("Prefix for static files of dashboard in jar.")
+val distDirectory = settingKey[File]("dist directory. This is like /target but for npm builds.")
+val dashboardZipArtifact = settingKey[Artifact]("Creates the artifact object")
+val dashboardFilesList = taskKey[Seq[File]]("List of static dashboard files")
+val dashboardFilesIndex = taskKey[File]("Creates an index of dashboard resources.")
+val prefixedDashboardResources = taskKey[Seq[File]]("Create resources containing dashboard files, prefixed.")
+
+lazy val `baker-http-dashboard`: Project = project.in(file("http/baker-http-dashboard"))
.enablePlugins(UniversalPlugin)
.settings(defaultModuleSettings)
.settings(
- name := "bakery-dashboard",
+ name := "baker-http-dashboard",
maintainer := "The Bakery Team",
- Universal / packageName := s"bakery-dashboard",
- Universal / mappings ++= Seq(file("dashboard.zip") -> "dashboard.zip"),
+ libraryDependencies ++= Seq(typeSafeConfig) ++ testDeps(
+ scalaTest,
+ logback
+ ),
+ Universal / packageName := name.value,
+ Universal / mappings += file("dashboard.zip") -> "dashboard.zip",
+ staticDashboardFilePrefix := "dashboard_static",
+ distDirectory := baseDirectory.value / "dist",
+ npmInputFiles := {
+ val sources = baseDirectory.value / "src" ** "*"
+ val projectConfiguration = baseDirectory.value * "*.json"
+ (sources.get() ++ projectConfiguration.get()).toSet
+ },
npmBuildTask := {
- val processBuilder = Process("./npm-build.sh", file("bakery/dashboard"))
- val process = processBuilder.run()
- if(process.exitValue() != 0) throw new Error(s"NPM failed with exit value ${process.exitValue()}")
- file("bakery/dashboard/dashboard.zip")
+ // Caches the npm ./npm-build.sh execution. Invalidation is done if either
+ // - anything is different in the baseDirectory / src, or files in the baseDirectory / *.json (compared using hash of file contents)
+ // - dist directory doesn't contain the same files as previously.
+ val cachedFunction = FileFunction.cached(streams.value.cacheDirectory / "npmBuild", inStyle = FileInfo.hash) { (in: Set[File]) =>
+ val processBuilder = Process("./npm-build.sh", baseDirectory.value)
+ val process = processBuilder.run()
+ if (process.exitValue() != 0) throw new Error(s"NPM failed with exit value ${process.exitValue()}")
+ val outputFiles = (distDirectory.value ** "*").get().toSet
+ outputFiles
+ }
+ cachedFunction(npmInputFiles.value)
+ distDirectory.value
+ },
+ zipDistDirectory := {
+ val inputDirectory = npmBuildTask.value
+ val targetZipFile = target.value / "dashboard.zip"
+ IO.zip(
+ sources = (inputDirectory ** "*").get().map(f => (f, inputDirectory.relativize(f).get.toString)),
+ outputZip = targetZipFile,
+ time = None)
+ targetZipFile
+ },
+ prefixedDashboardResources := {
+ val outputFolder = (Compile / resourceManaged).value / staticDashboardFilePrefix.value
+ IO.copyDirectory(npmBuildTask.value, outputFolder)
+ (outputFolder ** "*").get()
+ },
+ dashboardFilesList := {
+ val distDir = npmBuildTask.value
+ (distDir ** "*")
+ .filter(_.isFile).get()
},
- Compile / doc / sources := Seq.empty,
- Compile / packageDoc / mappings := Seq(),
- Compile / packageDoc / publishArtifact := false,
- Compile / packageSrc / publishArtifact := false,
- Compile / packageBin / publishArtifact := false,
- Universal / packageBin := npmBuildTask.value,
- addArtifact(Artifact("dashboard", "zip", "zip"), npmBuildTask),
- publish := (publish dependsOn (Universal / packageBin)).value,
- publishLocal := (publishLocal dependsOn (Universal / packageBin)).value
+ dashboardFilesIndex := {
+ val distDir = npmBuildTask.value
+ val resultFile = (Compile / resourceManaged).value / "dashboard_static_index"
+ IO.write(resultFile, dashboardFilesList.value
+ .map(file => s"${staticDashboardFilePrefix.value}/${distDir.relativize(file).get.toString}").mkString("\n"))
+ resultFile
+ },
+ dashboardZipArtifact := Artifact(name.value, "zip", "zip"),
+ sourceDirectory := baseDirectory.value / "src-scala",
+ // Note: resourceGenerators is not run by task compile. It is run by task package or run.
+ Compile / resourceGenerators += prefixedDashboardResources.taskValue,
+ Compile / resourceGenerators += Def.task { Seq(dashboardFilesIndex.value) }.taskValue,
+ cleanFiles += distDirectory.value,
+ addArtifact(dashboardZipArtifact, zipDistDirectory)
+ )
+
+lazy val `bakery-interaction-protocol`: Project = project.in(file("bakery/interaction-protocol"))
+ .settings(defaultModuleSettings)
+ .settings(scalaPBSettings)
+ .settings(
+ moduleName := "bakery-interaction-protocol",
+ libraryDependencies ++= Seq(
+ http4s,
+ http4sDsl,
+ http4sServer,
+ http4sClient,
+ http4sCirce,
+ http4sPrometheus,
+ prometheus,
+ prometheusJmx
+ )
)
+ .dependsOn(`baker-interface`)
-lazy val `bakery-state-k8s`: Project = project.in(file("bakery/baker-state-k8s"))
+lazy val `bakery-interaction-k8s-interaction-manager`: Project = project.in(file("bakery/interaction-k8s-interaction-manager"))
.settings(defaultModuleSettings)
.settings(yPartialUnificationSetting)
.settings(
- moduleName := "bakery-state-k8s",
+ moduleName := "bakery-interaction-k8s-interaction-manager",
libraryDependencies ++= Seq(
skuber
) ++ testDeps(
@@ -352,14 +426,15 @@ lazy val `bakery-state-k8s`: Project = project.in(file("bakery/baker-state-k8s")
)
.dependsOn(
`baker-akka-runtime`,
- `bakery-client`,
`baker-interface`,
`bakery-interaction-protocol`,
`baker-recipe-compiler`,
`baker-recipe-dsl`,
`baker-intermediate-language`,
- `bakery-dashboard`,
- `bakery-state` % "compile->compile;test->test"
+ `bakery-state`,
+ testScope(`bakery-state`),
+ testScope(`baker-http-client`),
+ testScope(`baker-http-server`)
)
@@ -372,9 +447,6 @@ lazy val `bakery-state`: Project = project.in(file("bakery/state"))
dockerExposedPorts ++= Seq(8080),
Docker / packageName := "bakery-state",
dockerBaseImage := "adoptopenjdk/openjdk11",
- Universal / mappings ++=
- directory(s"${(`bakery-dashboard` / baseDirectory).value.getAbsolutePath}/dist")
- .map(t => (t._1, t._2.replace("dist", "dashboard"))),
moduleName := "bakery-state",
libraryDependencies ++= Seq(
slf4jApi,
@@ -387,10 +459,6 @@ lazy val `bakery-state`: Project = project.in(file("bakery/state"))
akkaDiscovery,
akkaDiscoveryKube,
akkaPki,
- http4s,
- http4sDsl,
- http4sCirce,
- http4sServer,
kafkaClient
) ++ testDeps(
slf4jApi,
@@ -408,13 +476,13 @@ lazy val `bakery-state`: Project = project.in(file("bakery/state"))
)
.dependsOn(
`baker-akka-runtime`,
- `bakery-client`,
`baker-interface`,
`bakery-interaction-protocol`,
`baker-recipe-compiler`,
`baker-recipe-dsl`,
`baker-intermediate-language`,
- `bakery-dashboard`
+ `baker-http-server`,
+ `baker-http-dashboard`
)
lazy val `bakery-interaction`: Project = project.in(file("bakery/interaction"))
@@ -429,7 +497,7 @@ lazy val `bakery-interaction`: Project = project.in(file("bakery/interaction"))
http4sCirce,
circe,
catsEffect,
- catsCore,
+ catsCore
) ++ testDeps(
scalaTest,
logback
@@ -463,11 +531,21 @@ lazy val `bakery-interaction-spring`: Project = project.in(file("bakery/interact
lazy val baker: Project = project.in(file("."))
.settings(defaultModuleSettings)
.settings(
- crossScalaVersions := Nil,
+ crossScalaVersions := Nil
+ )
+ .aggregate(
+ // Core
+ `baker-types`, `baker-akka-runtime`, `baker-recipe-compiler`, `baker-recipe-dsl`, `baker-intermediate-language`,
+ `baker-interface`, `baker-annotations`, `baker-test`,
+ // Http
+ `baker-http-client`, `baker-http-server`, `baker-http-dashboard`,
+ // Bakery
+ `bakery-state`, `bakery-interaction`, `bakery-interaction-spring`, `bakery-interaction-protocol`,
+ `bakery-interaction-k8s-interaction-manager`,
+ // Examples
+ `baker-example`, `bakery-client-example`, `interaction-example-make-payment-and-ship-items`,
+ `interaction-example-reserve-items`, `bakery-kafka-listener-example`
)
- .aggregate(`baker-types`, `baker-akka-runtime`, `baker-recipe-compiler`, `baker-recipe-dsl`, `baker-intermediate-language`,
- `bakery-client`, `bakery-state`, `bakery-interaction`, `bakery-interaction-spring`, `bakery-interaction-protocol`,
- `bakery-state-k8s`, `baker-interface`, `bakery-dashboard`, `baker-annotations`, `baker-test`)
lazy val `baker-example`: Project = project
.in(file("examples/baker-example"))
@@ -475,6 +553,7 @@ lazy val `baker-example`: Project = project
.settings(commonSettings)
.settings(noPublishSettings)
.settings(yPartialUnificationSetting)
+ .settings(crossBuildSettings)
.settings(
moduleName := "baker-example",
libraryDependencies ++=
@@ -493,7 +572,8 @@ lazy val `baker-example`: Project = project
scalaCheck,
mockitoScala,
junitInterface,
- slf4jApi
+ slf4jApi,
+ akkaTestKit
)
)
.settings(dockerSettings)
@@ -506,8 +586,10 @@ lazy val `baker-example`: Project = project
lazy val `bakery-client-example`: Project = project
.in(file("examples/bakery-client-example"))
.enablePlugins(JavaAppPackaging)
- .settings(defaultModuleSettings)
+ .settings(commonSettings)
+ .settings(noPublishSettings)
.settings(yPartialUnificationSetting)
+ .settings(crossBuildSettings)
.settings(
moduleName := "bakery-client-example",
libraryDependencies ++=
@@ -529,11 +611,12 @@ lazy val `bakery-client-example`: Project = project
.settings(
Docker / packageName := "bakery-client-example"
)
- .dependsOn(`baker-types`, `bakery-client`, `baker-recipe-compiler`, `baker-recipe-dsl`)
+ .dependsOn(`baker-types`, `baker-http-client`, `baker-recipe-compiler`, `baker-recipe-dsl`)
lazy val `bakery-kafka-listener-example`: Project = project
.in(file("examples/bakery-kafka-listener-example"))
.enablePlugins(JavaAppPackaging)
+ .settings(noPublishSettings)
.settings(defaultModuleSettings)
.settings(yPartialUnificationSetting)
.settings(
@@ -555,11 +638,12 @@ lazy val `bakery-kafka-listener-example`: Project = project
.settings(
Docker / packageName := "bakery-kafka-listener-example"
)
- .dependsOn(`baker-types`, `bakery-client`, `baker-recipe-compiler`, `baker-recipe-dsl`)
+ .dependsOn(`baker-types`, `baker-http-client`, `baker-recipe-compiler`, `baker-recipe-dsl`)
lazy val `interaction-example-reserve-items`: Project = project.in(file("examples/bakery-interaction-examples/reserve-items"))
.enablePlugins(JavaAppPackaging)
.enablePlugins(bakery.sbt.BuildInteractionDockerImageSBTPlugin)
+ .settings(noPublishSettings)
.settings(defaultModuleSettings)
.settings(yPartialUnificationSetting)
.settings(
@@ -580,6 +664,7 @@ lazy val `interaction-example-reserve-items`: Project = project.in(file("example
lazy val `interaction-example-make-payment-and-ship-items`: Project = project.in(file("examples/bakery-interaction-examples/make-payment-and-ship-items"))
.enablePlugins(JavaAppPackaging)
.enablePlugins(bakery.sbt.BuildInteractionDockerImageSBTPlugin)
+ .settings(noPublishSettings)
.settings(defaultModuleSettings)
.settings(yPartialUnificationSetting)
.settings(
@@ -613,7 +698,7 @@ lazy val `bakery-integration-tests`: Project = project.in(file("bakery/integrati
)
)
.dependsOn(
- `bakery-client`,
+ `baker-http-client`,
`bakery-client-example`,
`interaction-example-make-payment-and-ship-items`,
`interaction-example-reserve-items`)
diff --git a/core/README.MD b/core/README.MD
new file mode 100644
index 000000000..8cba1b018
--- /dev/null
+++ b/core/README.MD
@@ -0,0 +1,3 @@
+The `core` directory contains all modules pertaining to baker.
+
+It contains the recipe DSLs, as well as the runtime implementations (in-memory based on `cats`, as well as the akka-based runtime).
diff --git a/core/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala b/core/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala
index 0f1c0d6fd..66139e52e 100644
--- a/core/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala
+++ b/core/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala
@@ -19,6 +19,11 @@ import scala.concurrent.{Await, Future}
class Baker(private val baker: scaladsl.Baker) extends common.Baker[CompletableFuture] with JavaApi with AutoCloseable {
+ /**
+ * Get underlying baker instance, which provides the scala api.
+ */
+ def getScalaBaker: scaladsl.Baker = baker
+
override type SensoryEventResultType = SensoryEventResult
override type EventResolutionsType = EventResolutions
diff --git a/core/baker-test/src/main/scala/com/ing/baker/test/RecipeAssert.scala b/core/baker-test/src/main/scala/com/ing/baker/test/RecipeAssert.scala
index 58d893e44..4b39939a1 100644
--- a/core/baker-test/src/main/scala/com/ing/baker/test/RecipeAssert.scala
+++ b/core/baker-test/src/main/scala/com/ing/baker/test/RecipeAssert.scala
@@ -116,14 +116,7 @@ object RecipeAssert {
private implicit def toScala(duration: java.time.Duration): Duration =
Duration.fromNanos(duration.toNanos)
- // hack for now as there is no way to convert java baker to scala baker
private implicit def toScala(baker: javadsl.Baker): scaladsl.Baker = {
- val field = classOf[javadsl.Baker].getDeclaredField("baker")
- try {
- field.setAccessible(true)
- field.get(baker).asInstanceOf[scaladsl.Baker]
- } finally {
- field.setAccessible(false)
- }
+ baker.getScalaBaker
}
}
diff --git a/examples/baker-example/src/main/resources/application.conf b/examples/baker-example/src/main/resources/application.conf
index 70cd23342..2cbb30706 100644
--- a/examples/baker-example/src/main/resources/application.conf
+++ b/examples/baker-example/src/main/resources/application.conf
@@ -36,15 +36,6 @@ baker {
seed-nodes = [
"akka://"${service.actorSystemName}"@"${service.seedHost}":"${service.seedPort}]
}
-
- event-sink {
- class: "com.ing.bakery.baker.KafkaEventSink",
- bootstrap-servers: "kafka-event-sink:9092",
- bootstrap-servers: ${?KAFKA_EVENT_SINK_BOOTSTRAP_SERVERS},
- topic: "events",
- topic: ${?KAFKA_EVENT_SINK_TOPIC}
- }
-
}
cassandra-journal.contact-points.0 = "127.0.0.1"
diff --git a/examples/baker-example/src/test/java/webshop/JWebshopRecipeTests.java b/examples/baker-example/src/test/java/webshop/JWebshopRecipeTests.java
index e5b37c9a1..84f32c582 100644
--- a/examples/baker-example/src/test/java/webshop/JWebshopRecipeTests.java
+++ b/examples/baker-example/src/test/java/webshop/JWebshopRecipeTests.java
@@ -1,15 +1,12 @@
package webshop;
-import akka.actor.ActorSystem;
import com.google.common.collect.ImmutableList;
import com.ing.baker.compiler.RecipeCompiler;
import com.ing.baker.il.CompiledRecipe;
-import com.ing.baker.runtime.akka.AkkaBaker;
import com.ing.baker.runtime.inmemory.InMemoryBaker;
import com.ing.baker.runtime.javadsl.Baker;
import com.ing.baker.runtime.javadsl.EventInstance;
import com.ing.baker.runtime.javadsl.EventMoment;
-import com.typesafe.config.ConfigFactory;
import org.junit.Test;
import scala.Console;
import webshop.simple.*;
@@ -54,10 +51,7 @@ public void shouldRunSimpleInstance() throws ExecutionException, InterruptedExce
new ShipItemsInstance());
// Setup the Baker
- Baker baker = AkkaBaker.java(
- ConfigFactory.load(),
- ActorSystem.apply("BakerActorSystem"),
- implementations);
+ Baker baker = InMemoryBaker.java(implementations);
// Create the sensory events
List- items = new ArrayList<>(2);
@@ -136,4 +130,6 @@ public void shouldRunSimpleInstanceMockitoSample() throws InterruptedException,
blockedResult.contains("ShippingAddressReceived") &&
blockedResult.contains("ShippingConfirmed"));
}
+
+
}
diff --git a/examples/baker-example/src/test/resources/application.conf b/examples/baker-example/src/test/resources/application.conf
index 385178f20..fdeab6144 100644
--- a/examples/baker-example/src/test/resources/application.conf
+++ b/examples/baker-example/src/test/resources/application.conf
@@ -1,5 +1,3 @@
-include "baker.conf"
-
akka {
log-config-on-start = off
@@ -25,6 +23,14 @@ akka {
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
}
+akka {
+ remote.artery {
+ transport = tcp
+ canonical.hostname = "127.0.0.1"
+ canonical.port = 0
+ }
+}
+
baker {
actor.retention-check-interval = 100 milliseconds
encryption {
@@ -35,13 +41,6 @@ baker {
localhost.port = 8081
}
- event-sink {
- class: "com.ing.bakery.baker.KafkaEventSink",
- bootstrap-servers: "kafka-event-sink:9092",
- bootstrap-servers: ${?KAFKA_EVENT_SINK_BOOTSTRAP_SERVERS},
- topic: "events",
- topic: ${?KAFKA_EVENT_SINK_TOPIC}
- }
recipe-manager-type = "actor"
actor.provider = "local"
allow-adding-recipe-without-requiring-instances = false
diff --git a/examples/baker-example/src/test/scala/webshop/simple/WebshopRecipeSpec.scala b/examples/baker-example/src/test/scala/webshop/simple/WebshopRecipeSpec.scala
index 161f759a7..088848f8b 100644
--- a/examples/baker-example/src/test/scala/webshop/simple/WebshopRecipeSpec.scala
+++ b/examples/baker-example/src/test/scala/webshop/simple/WebshopRecipeSpec.scala
@@ -1,36 +1,50 @@
package webshop.simple
import akka.actor.ActorSystem
+import akka.testkit.TestKit
import cats.effect.{ContextShift, IO}
import com.ing.baker.compiler.RecipeCompiler
import com.ing.baker.runtime.akka.AkkaBaker
import com.ing.baker.runtime.akka.internal.CachingInteractionManager
import com.ing.baker.runtime.common.RecipeRecord
import com.ing.baker.runtime.scaladsl.{Baker, EventInstance, InteractionInstance}
-import org.scalatest.flatspec.AsyncFlatSpec
+import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpecLike
import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}
-class WebshopRecipeSpec extends AsyncFlatSpec with Matchers {
+class WebshopRecipeSpec extends TestKit(ActorSystem("baker-webshop-system")) with Matchers with AnyWordSpecLike with BeforeAndAfterAll {
- "The WebshopRecipeReflection" should "compile the recipe without errors" in {
- RecipeCompiler.compileRecipe(SimpleWebshopRecipeReflection.recipe)
- Future.successful(succeed)
+ implicit val ec : ExecutionContext = system.dispatcher
+
+ override def afterAll(): Unit = {
+ TestKit.shutdownActorSystem(system)
}
- "The WebshopRecipe" should "compile the recipe without errors" in {
- RecipeCompiler.compileRecipe(SimpleWebshopRecipe.recipe)
- Future.successful(succeed)
+ "The WebshopRecipeReflection" should {
+ "compile the recipe without errors" in {
+ RecipeCompiler.compileRecipe(SimpleWebshopRecipeReflection.recipe)
+ Future.successful(succeed)
+ }
}
- it should "visualize the recipe" in {
- val compiled = RecipeCompiler.compileRecipe(SimpleWebshopRecipe.recipe)
- val viz: String = compiled.getRecipeVisualization
- println(Console.GREEN + s"Recipe visualization, paste this into webgraphviz.com:")
- println(viz + Console.RESET)
- Future.successful(succeed)
+ "The WebshopRecipe" should {
+ "compile the recipe without errors" in {
+ RecipeCompiler.compileRecipe(SimpleWebshopRecipe.recipe)
+ Future.successful(succeed)
+ }
+ }
+
+ it should {
+ "visualize the recipe" in {
+ val compiled = RecipeCompiler.compileRecipe(SimpleWebshopRecipe.recipe)
+ val viz: String = compiled.getRecipeVisualization
+ println(Console.GREEN + s"Recipe visualization, paste this into webgraphviz.com:")
+ println(viz + Console.RESET)
+ Future.successful(succeed)
+ }
}
trait ReserveItems {
@@ -57,41 +71,42 @@ class WebshopRecipeSpec extends AsyncFlatSpec with Matchers {
}
}
- it should "reserve items in happy conditions" in {
- val system: ActorSystem = ActorSystem("baker-webshop-system")
- implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
-
- val reserveItemsInstance: InteractionInstance =
- InteractionInstance.unsafeFrom(new ReserveItemsMock)
- val baker: Baker = AkkaBaker.localDefault(system, CachingInteractionManager(reserveItemsInstance))
-
- val compiled = RecipeCompiler.compileRecipe(SimpleWebshopRecipe.recipe)
- val recipeInstanceId: String = UUID.randomUUID().toString
-
- val orderId: String = "order-id"
- val items: List[String] = List("item1", "item2")
-
- val orderPlaced = EventInstance
- .unsafeFrom(SimpleWebshopRecipeReflection.OrderPlaced(orderId, items))
- val paymentMade = EventInstance
- .unsafeFrom(SimpleWebshopRecipeReflection.PaymentMade())
-
-
- for {
- recipeId <- baker.addRecipe(RecipeRecord.of(compiled))
- _ <- baker.bake(recipeId, recipeInstanceId)
- _ <- baker.fireEventAndResolveWhenCompleted(
- recipeInstanceId, orderPlaced)
- _ <- baker.fireEventAndResolveWhenCompleted(
- recipeInstanceId, paymentMade)
- state <- baker.getRecipeInstanceState(recipeInstanceId)
- provided = state
- .ingredients
- .find(_._1 == "reservedItems")
- .map(_._2.as[List[String]])
- .map(_.mkString(", "))
- .getOrElse("No reserved items")
-
- } yield provided shouldBe items.mkString(", ")
+ it should {
+ "reserve items in happy conditions" in {
+ implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
+
+ val reserveItemsInstance: InteractionInstance =
+ InteractionInstance.unsafeFrom(new ReserveItemsMock)
+ val baker: Baker = AkkaBaker.localDefault(system, CachingInteractionManager(reserveItemsInstance))
+
+ val compiled = RecipeCompiler.compileRecipe(SimpleWebshopRecipe.recipe)
+ val recipeInstanceId: String = UUID.randomUUID().toString
+
+ val orderId: String = "order-id"
+ val items: List[String] = List("item1", "item2")
+
+ val orderPlaced = EventInstance
+ .unsafeFrom(SimpleWebshopRecipeReflection.OrderPlaced(orderId, items))
+ val paymentMade = EventInstance
+ .unsafeFrom(SimpleWebshopRecipeReflection.PaymentMade())
+
+
+ for {
+ recipeId <- baker.addRecipe(RecipeRecord.of(compiled))
+ _ <- baker.bake(recipeId, recipeInstanceId)
+ _ <- baker.fireEventAndResolveWhenCompleted(
+ recipeInstanceId, orderPlaced)
+ _ <- baker.fireEventAndResolveWhenCompleted(
+ recipeInstanceId, paymentMade)
+ state <- baker.getRecipeInstanceState(recipeInstanceId)
+ provided = state
+ .ingredients
+ .find(_._1 == "reservedItems")
+ .map(_._2.as[List[String]])
+ .map(_.mkString(", "))
+ .getOrElse("No reserved items")
+
+ } yield provided shouldBe items.mkString(", ")
+ }
}
}
diff --git a/examples/bakery-client-example/src/main/scala/webshop/webservice/Main.scala b/examples/bakery-client-example/src/main/scala/webshop/webservice/Main.scala
index d30e77c30..4e3829b4f 100644
--- a/examples/bakery-client-example/src/main/scala/webshop/webservice/Main.scala
+++ b/examples/bakery-client-example/src/main/scala/webshop/webservice/Main.scala
@@ -5,7 +5,7 @@ import java.util.concurrent.Executors
import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._
import com.ing.baker.compiler.RecipeCompiler
-import com.ing.bakery.scaladsl.BakerClient
+import com.ing.baker.http.client.scaladsl.BakerClient
import com.typesafe.config.ConfigFactory
import org.http4s.Uri
import org.http4s.server.blaze.BlazeServerBuilder
diff --git a/http/README.md b/http/README.md
new file mode 100644
index 000000000..127a01243
--- /dev/null
+++ b/http/README.md
@@ -0,0 +1,5 @@
+The `http` directory contains the modules for exposing or consuming baker using http(s). This allows for baker to be called
+from other APIs (see the `bakery` directory) or even from non-jvm applications (for example javascript).
+
+The `http` directory also contains the `baker-http-dashboard` project which uses the endpoints exposed by the `baker-http-server` library to allow you
+to view added recipes, inspect and execute interactions manually, or inspect the status of process instances (executed recipes).
diff --git a/bakery/client/src/main/resources/reference.conf b/http/baker-http-client/src/main/resources/reference.conf
similarity index 100%
rename from bakery/client/src/main/resources/reference.conf
rename to http/baker-http-client/src/main/resources/reference.conf
diff --git a/bakery/client/src/main/scala/com/ing/bakery/common/FailoverState.scala b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/FailoverState.scala
similarity index 87%
rename from bakery/client/src/main/scala/com/ing/bakery/common/FailoverState.scala
rename to http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/FailoverState.scala
index 538b1e498..91dec0ba9 100644
--- a/bakery/client/src/main/scala/com/ing/bakery/common/FailoverState.scala
+++ b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/FailoverState.scala
@@ -1,6 +1,6 @@
-package com.ing.bakery.common
+package com.ing.baker.http.client.common
-import com.ing.bakery.scaladsl.EndpointConfig
+import com.ing.baker.http.client.scaladsl.EndpointConfig
import com.typesafe.scalalogging.LazyLogging
import org.http4s.Uri
@@ -26,4 +26,4 @@ sealed class FailoverState(var endpoint: EndpointConfig) extends LazyLogging {
}
def uri: Uri = endpoint.hosts(currentPosition.get())
-}
\ No newline at end of file
+}
diff --git a/bakery/client/src/main/scala/com/ing/bakery/common/FailoverUtils.scala b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/FailoverUtils.scala
similarity index 96%
rename from bakery/client/src/main/scala/com/ing/bakery/common/FailoverUtils.scala
rename to http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/FailoverUtils.scala
index 1c019075b..06789ee9f 100644
--- a/bakery/client/src/main/scala/com/ing/bakery/common/FailoverUtils.scala
+++ b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/FailoverUtils.scala
@@ -1,11 +1,11 @@
-package com.ing.bakery.common
+package com.ing.baker.http.client.common
import cats.effect.{ContextShift, IO, Timer}
import cats.implicits._
+import com.ing.baker.http.client.scaladsl.{EndpointConfig, ResponseError}
import com.ing.baker.runtime.common.BakerException
import com.ing.baker.runtime.common.BakerException.NoSuchProcessException
import com.ing.baker.runtime.scaladsl.BakerResult
-import com.ing.bakery.scaladsl.{EndpointConfig, ResponseError}
import com.typesafe.config.ConfigFactory
import com.typesafe.scalalogging.LazyLogging
import io.circe.Decoder
diff --git a/bakery/client/src/main/scala/com/ing/bakery/common/KeystoreConfig.scala b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/KeystoreConfig.scala
similarity index 97%
rename from bakery/client/src/main/scala/com/ing/bakery/common/KeystoreConfig.scala
rename to http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/KeystoreConfig.scala
index b937948af..538a8ee00 100644
--- a/bakery/client/src/main/scala/com/ing/bakery/common/KeystoreConfig.scala
+++ b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/KeystoreConfig.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.common
+package com.ing.baker.http.client.common
import java.io.{File, FileInputStream, InputStream}
import java.security.KeyStore
diff --git a/bakery/client/src/main/scala/com/ing/bakery/common/TLSConfig.scala b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/TLSConfig.scala
similarity index 94%
rename from bakery/client/src/main/scala/com/ing/bakery/common/TLSConfig.scala
rename to http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/TLSConfig.scala
index 03e47ef87..5b7f5f874 100644
--- a/bakery/client/src/main/scala/com/ing/bakery/common/TLSConfig.scala
+++ b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/common/TLSConfig.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.common
+package com.ing.baker.http.client.common
import java.security.SecureRandom
diff --git a/bakery/client/src/main/scala/com/ing/bakery/javadsl/BakerClient.scala b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/javadsl/BakerClient.scala
similarity index 92%
rename from bakery/client/src/main/scala/com/ing/bakery/javadsl/BakerClient.scala
rename to http/baker-http-client/src/main/scala/com/ing/baker/http/client/javadsl/BakerClient.scala
index d47543ac3..319ba7dd9 100644
--- a/bakery/client/src/main/scala/com/ing/bakery/javadsl/BakerClient.scala
+++ b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/javadsl/BakerClient.scala
@@ -1,10 +1,9 @@
-package com.ing.bakery.javadsl
+package com.ing.baker.http.client.javadsl
import cats.effect.{ContextShift, IO, Timer}
+import com.ing.baker.http.client.common.TLSConfig
+import com.ing.baker.http.client.scaladsl.{BakerClient => ScalaClient, EndpointConfig}
import com.ing.baker.runtime.javadsl.{Baker => JavaBaker}
-import com.ing.bakery.common.TLSConfig
-import com.ing.bakery.scaladsl
-import com.ing.bakery.scaladsl.EndpointConfig
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.{Request, Uri}
@@ -51,7 +50,7 @@ object BakerClient {
sslContext = sslContext)
.resource
.map { client =>
- new scaladsl.BakerClient(
+ new ScalaClient(
client = client,
EndpointConfig(hosts.asScala.map(Uri.unsafeFromString).toIndexedSeq, apiUrlPrefix, apiLoggingEnabled),
if (fallbackHosts.size == 0) None
@@ -66,4 +65,4 @@ object BakerClient {
FutureConverters.toJava(future).toCompletableFuture
}
-}
\ No newline at end of file
+}
diff --git a/bakery/client/src/main/scala/com/ing/bakery/scaladsl/BakerClient.scala b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/scaladsl/BakerClient.scala
similarity index 98%
rename from bakery/client/src/main/scala/com/ing/bakery/scaladsl/BakerClient.scala
rename to http/baker-http-client/src/main/scala/com/ing/baker/http/client/scaladsl/BakerClient.scala
index d35436fcc..6acd3ec0d 100644
--- a/bakery/client/src/main/scala/com/ing/bakery/scaladsl/BakerClient.scala
+++ b/http/baker-http-client/src/main/scala/com/ing/baker/http/client/scaladsl/BakerClient.scala
@@ -1,8 +1,7 @@
-package com.ing.bakery.scaladsl
+package com.ing.baker.http.client.scaladsl
import cats.effect.{ContextShift, IO, Resource, Timer}
import com.ing.baker.il.RecipeVisualStyle
-import com.ing.baker.runtime.common.BakerException.SingleInteractionExecutionFailedException
import com.ing.baker.runtime.common.{BakerException, RecipeRecord, SensoryEventStatus, Utils}
import com.ing.baker.runtime.scaladsl.{BakerEvent, BakerResult, EncodedRecipe, EventInstance, EventMoment, EventResolutions, IngredientInstance, InteractionExecutionResult, InteractionInstanceDescriptor, RecipeEventMetadata, RecipeInformation, RecipeInstanceMetadata, RecipeInstanceState, SensoryEventResult, Baker => ScalaBaker}
import com.ing.baker.runtime.serialization.InteractionExecution
@@ -10,8 +9,8 @@ import com.ing.baker.runtime.serialization.InteractionExecutionJsonCodecs._
import com.ing.baker.runtime.serialization.JsonDecoders._
import com.ing.baker.runtime.serialization.JsonEncoders._
import com.ing.baker.types.Value
-import com.ing.bakery.common.FailoverUtils._
-import com.ing.bakery.common.{FailoverState, TLSConfig}
+import com.ing.baker.http.client.common.FailoverUtils._
+import com.ing.baker.http.client.common.{FailoverState, TLSConfig}
import com.typesafe.scalalogging.LazyLogging
import io.circe.Decoder
import org.http4s.Method._
diff --git a/bakery/client/src/test/resources/logback-test.xml b/http/baker-http-client/src/test/resources/logback-test.xml
similarity index 100%
rename from bakery/client/src/test/resources/logback-test.xml
rename to http/baker-http-client/src/test/resources/logback-test.xml
diff --git a/bakery/client/src/test/resources/test-certs/client.jks b/http/baker-http-client/src/test/resources/test-certs/client.jks
similarity index 100%
rename from bakery/client/src/test/resources/test-certs/client.jks
rename to http/baker-http-client/src/test/resources/test-certs/client.jks
diff --git a/bakery/client/src/test/resources/test-certs/server.jks b/http/baker-http-client/src/test/resources/test-certs/server.jks
similarity index 100%
rename from bakery/client/src/test/resources/test-certs/server.jks
rename to http/baker-http-client/src/test/resources/test-certs/server.jks
diff --git a/bakery/client/src/test/scala/com/ing/bakery/client/BakerClientSpec.scala b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/BakerClientSpec.scala
similarity index 92%
rename from bakery/client/src/test/scala/com/ing/bakery/client/BakerClientSpec.scala
rename to http/baker-http-client/src/test/scala/com/ing/baker/http/client/BakerClientSpec.scala
index d558def34..16253f733 100644
--- a/bakery/client/src/test/scala/com/ing/bakery/client/BakerClientSpec.scala
+++ b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/BakerClientSpec.scala
@@ -1,18 +1,18 @@
-package com.ing.bakery.client
+package com.ing.baker.http.client
import cats.effect.concurrent.{MVar, MVar2}
import cats.effect.{IO, Resource}
+import com.ing.baker.http.client.common.{KeystoreConfig, TLSConfig}
+import com.ing.baker.http.client.javadsl.{BakerClient => JavaClient}
+import com.ing.baker.http.client.scaladsl.{EndpointConfig, BakerClient => ScalaClient}
import com.ing.baker.runtime.scaladsl.BakerResult
import com.ing.baker.runtime.serialization.JsonEncoders._
-import com.ing.bakery.common.{KeystoreConfig, TLSConfig}
-import com.ing.bakery.javadsl
-import com.ing.bakery.scaladsl.{BakerClient, EndpointConfig}
+import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.server.Router
import org.http4s.server.blaze._
-import org.http4s._
import org.scalatest.ConfigMap
import java.net.InetSocketAddress
@@ -89,7 +89,7 @@ class BakerClientSpec extends BakeryFunSpec {
val host = Uri.unsafeFromString(s"https://localhost:${context.serverAddress.getPort}/")
val testHeader = Header("X-Test", "Foo")
val filter: Request[IO] => Request[IO] = _.putHeaders(testHeader)
- BakerClient.resource(host, "/api/bakery", executionContext, List(filter), Some(clientTLSConfig)).use { client =>
+ ScalaClient.resource(host, "/api/bakery", executionContext, List(filter), Some(clientTLSConfig)).use { client =>
for {
_ <- IO.fromFuture(IO(client.getAllRecipeInstancesMetadata))
headers <- context.receivedHeaders
@@ -102,7 +102,7 @@ class BakerClientSpec extends BakeryFunSpec {
val uri2 = Uri.unsafeFromString(s"https://invaliddomainname:445")
val uri3 = uri1 / "nowWorking"
- BakerClient.resourceBalanced(
+ ScalaClient.resourceBalanced(
endpointConfig = EndpointConfig(IndexedSeq(uri3, uri2, uri1)),
executionContext = executionContext,
filters = List.empty,
@@ -120,7 +120,7 @@ class BakerClientSpec extends BakeryFunSpec {
val filter: java.util.function.Function[Request[IO], Request[IO]] = _.putHeaders(testHeader)
for {
client <- IO.fromFuture(IO(FutureConverters.toScala(
- javadsl.BakerClient.build(List(host).asJava, "/api/bakery",
+ JavaClient.build(List(host).asJava, "/api/bakery",
List().asJava, "", List(filter).asJava, java.util.Optional.of(clientTLSConfig), true))))
_ <- IO.fromFuture(IO(FutureConverters.toScala(client.getAllRecipeInstancesMetadata)))
headers <- context.receivedHeaders
diff --git a/bakery/client/src/test/scala/com/ing/bakery/client/BakeryFunSpec.scala b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/BakeryFunSpec.scala
similarity index 98%
rename from bakery/client/src/test/scala/com/ing/bakery/client/BakeryFunSpec.scala
rename to http/baker-http-client/src/test/scala/com/ing/baker/http/client/BakeryFunSpec.scala
index 34f699b69..2817ad42f 100644
--- a/bakery/client/src/test/scala/com/ing/bakery/client/BakeryFunSpec.scala
+++ b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/BakeryFunSpec.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.client
+package com.ing.baker.http.client
import cats.effect.{ContextShift, IO, Resource, Timer}
import org.scalactic.source
@@ -68,4 +68,4 @@ abstract class BakeryFunSpec extends FixtureAsyncFunSpecLike {
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
test.apply(argumentsBuilder(test.configMap))
-}
\ No newline at end of file
+}
diff --git a/bakery/client/src/test/scala/com/ing/bakery/common/FailoverStateSpec.scala b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/common/FailoverStateSpec.scala
similarity index 96%
rename from bakery/client/src/test/scala/com/ing/bakery/common/FailoverStateSpec.scala
rename to http/baker-http-client/src/test/scala/com/ing/baker/http/client/common/FailoverStateSpec.scala
index 762278b14..619fbbd4a 100644
--- a/bakery/client/src/test/scala/com/ing/bakery/common/FailoverStateSpec.scala
+++ b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/common/FailoverStateSpec.scala
@@ -1,6 +1,6 @@
-package com.ing.bakery.common
+package com.ing.baker.http.client.common
-import com.ing.bakery.scaladsl.EndpointConfig
+import com.ing.baker.http.client.scaladsl.EndpointConfig
import org.http4s.Uri
import org.scalatest.funspec.AnyFunSpec
@@ -122,4 +122,4 @@ class FailoverStateSpec extends AnyFunSpec {
}
-}
\ No newline at end of file
+}
diff --git a/bakery/client/src/test/scala/com/ing/bakery/common/FailoverUtilsSpec.scala b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/common/FailoverUtilsSpec.scala
similarity index 97%
rename from bakery/client/src/test/scala/com/ing/bakery/common/FailoverUtilsSpec.scala
rename to http/baker-http-client/src/test/scala/com/ing/baker/http/client/common/FailoverUtilsSpec.scala
index 951a8bedd..e68e87014 100644
--- a/bakery/client/src/test/scala/com/ing/bakery/common/FailoverUtilsSpec.scala
+++ b/http/baker-http-client/src/test/scala/com/ing/baker/http/client/common/FailoverUtilsSpec.scala
@@ -1,10 +1,10 @@
-package com.ing.bakery.common
+package com.ing.baker.http.client.common
import cats.effect.{ContextShift, IO, Resource, Timer}
+import com.ing.baker.http.client.scaladsl.EndpointConfig
import com.ing.baker.runtime.common.BakerException.NoSuchProcessException
import com.ing.baker.runtime.scaladsl.BakerResult
import com.ing.baker.runtime.serialization.JsonEncoders._
-import com.ing.bakery.scaladsl.EndpointConfig
import org.http4s.Method.GET
import org.http4s._
import org.http4s.circe.jsonEncoderOf
@@ -27,7 +27,7 @@ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
@nowarn
class FailoverUtilsSpec extends FixtureAsyncFunSpec {
- import FailoverUtils._
+ import com.ing.baker.http.client.common.FailoverUtils._
implicit val ec: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global
implicit val contextShift: ContextShift[IO] =
IO.contextShift(ec)
@@ -177,4 +177,4 @@ class FailoverUtilsSpec extends FixtureAsyncFunSpec {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/bakery/dashboard/.browserslistrc b/http/baker-http-dashboard/.browserslistrc
similarity index 100%
rename from bakery/dashboard/.browserslistrc
rename to http/baker-http-dashboard/.browserslistrc
diff --git a/bakery/dashboard/.editorconfig b/http/baker-http-dashboard/.editorconfig
similarity index 100%
rename from bakery/dashboard/.editorconfig
rename to http/baker-http-dashboard/.editorconfig
diff --git a/bakery/dashboard/.eslintrc.json b/http/baker-http-dashboard/.eslintrc.json
similarity index 100%
rename from bakery/dashboard/.eslintrc.json
rename to http/baker-http-dashboard/.eslintrc.json
diff --git a/bakery/dashboard/.gitignore b/http/baker-http-dashboard/.gitignore
similarity index 100%
rename from bakery/dashboard/.gitignore
rename to http/baker-http-dashboard/.gitignore
diff --git a/bakery/dashboard/.nvmrc b/http/baker-http-dashboard/.nvmrc
similarity index 100%
rename from bakery/dashboard/.nvmrc
rename to http/baker-http-dashboard/.nvmrc
diff --git a/bakery/dashboard/README.md b/http/baker-http-dashboard/README.md
similarity index 100%
rename from bakery/dashboard/README.md
rename to http/baker-http-dashboard/README.md
diff --git a/bakery/dashboard/angular.json b/http/baker-http-dashboard/angular.json
similarity index 100%
rename from bakery/dashboard/angular.json
rename to http/baker-http-dashboard/angular.json
diff --git a/bakery/dashboard/e2e/protractor.conf.js b/http/baker-http-dashboard/e2e/protractor.conf.js
similarity index 100%
rename from bakery/dashboard/e2e/protractor.conf.js
rename to http/baker-http-dashboard/e2e/protractor.conf.js
diff --git a/bakery/dashboard/e2e/src/app.e2e-spec.ts b/http/baker-http-dashboard/e2e/src/app.e2e-spec.ts
similarity index 100%
rename from bakery/dashboard/e2e/src/app.e2e-spec.ts
rename to http/baker-http-dashboard/e2e/src/app.e2e-spec.ts
diff --git a/bakery/dashboard/e2e/src/app.po.ts b/http/baker-http-dashboard/e2e/src/app.po.ts
similarity index 100%
rename from bakery/dashboard/e2e/src/app.po.ts
rename to http/baker-http-dashboard/e2e/src/app.po.ts
diff --git a/bakery/dashboard/e2e/tsconfig.json b/http/baker-http-dashboard/e2e/tsconfig.json
similarity index 100%
rename from bakery/dashboard/e2e/tsconfig.json
rename to http/baker-http-dashboard/e2e/tsconfig.json
diff --git a/bakery/dashboard/karma.conf.js b/http/baker-http-dashboard/karma.conf.js
similarity index 100%
rename from bakery/dashboard/karma.conf.js
rename to http/baker-http-dashboard/karma.conf.js
diff --git a/bakery/dashboard/npm-build.sh b/http/baker-http-dashboard/npm-build.sh
similarity index 74%
rename from bakery/dashboard/npm-build.sh
rename to http/baker-http-dashboard/npm-build.sh
index b10bc8ac7..8e61b24bb 100755
--- a/bakery/dashboard/npm-build.sh
+++ b/http/baker-http-dashboard/npm-build.sh
@@ -4,5 +4,4 @@ npm install
npx ng build
#npm run test
#npm run lint
-cd dist
-zip -r ../dashboard.zip *
+
diff --git a/bakery/dashboard/package.json b/http/baker-http-dashboard/package.json
similarity index 98%
rename from bakery/dashboard/package.json
rename to http/baker-http-dashboard/package.json
index 8fd8aec54..6f7d03a91 100644
--- a/bakery/dashboard/package.json
+++ b/http/baker-http-dashboard/package.json
@@ -36,7 +36,7 @@
"@angular-eslint/eslint-plugin-template": "14.0.2",
"@angular-eslint/schematics": "14.0.2",
"@angular-eslint/template-parser": "14.0.2",
- "@angular/cli": "^14.0.6",
+ "@angular/cli": "^14.1.1",
"@angular/compiler-cli": "^14.0.6",
"@angular/language-service": "^14.0.6",
"@angular/localize": "^14.0.6",
diff --git a/http/baker-http-dashboard/src-scala/main/resources/reference.conf b/http/baker-http-dashboard/src-scala/main/resources/reference.conf
new file mode 100644
index 000000000..13482dc4e
--- /dev/null
+++ b/http/baker-http-dashboard/src-scala/main/resources/reference.conf
@@ -0,0 +1,6 @@
+baker.dashboard {
+ enabled = true
+ application-name = "Baker OSS"
+ cluster-information {
+ }
+}
diff --git a/http/baker-http-dashboard/src-scala/main/scala/com/ing/baker/http/Dashboard.scala b/http/baker-http-dashboard/src-scala/main/scala/com/ing/baker/http/Dashboard.scala
new file mode 100644
index 000000000..806375b04
--- /dev/null
+++ b/http/baker-http-dashboard/src-scala/main/scala/com/ing/baker/http/Dashboard.scala
@@ -0,0 +1,38 @@
+package com.ing.baker.http
+
+import scala.io.Source
+import scala.util.Try
+import scala.util.matching.Regex
+
+object Dashboard {
+ private val DASHBOARD_PREFIX = "dashboard_static/"
+
+ /**
+ * List of static files of the dashboard.
+ */
+ lazy val files : Seq[String] =
+ Try(Source.fromResource("dashboard_static_index").getLines().map(_.replace(DASHBOARD_PREFIX, "")).toIndexedSeq)
+ .getOrElse(throw new IllegalStateException("Expected list of dashboard files to be available under 'dashboard_static_index"))
+
+ /**
+ * Http paths that should serve the index page.
+ */
+ val indexPattern: Regex = "^(/)?|(/recipes)|(/interactions)|(/instances(/.+)?)$".r
+
+ /**
+ * Get URL to resource from filename. Do not specify the dashboard prefix, as it is automatically added.
+ * Uses a whitelist of files to prevent any unauthorized access of resources by a malicious user.
+ */
+ def safeGetResourcePath(fileName: String) : Option[String] =
+ files.find(_ == fileName).map(DASHBOARD_PREFIX + _)
+
+ def dashboardConfigJson(apiPath: String, dashboardConfiguration: DashboardConfiguration) : String =
+ s"""{
+ | "applicationName": "${dashboardConfiguration.applicationName}",
+ | "apiPath": "${apiPath}",
+ | "clusterInformation": {
+ | ${dashboardConfiguration.clusterInformation.map{ case (key, value) => s""" "$key": "$value""""}.mkString(",\n ")}
+ | }
+ |}
+ |""".stripMargin
+}
diff --git a/http/baker-http-dashboard/src-scala/main/scala/com/ing/baker/http/DashboardConfiguration.scala b/http/baker-http-dashboard/src-scala/main/scala/com/ing/baker/http/DashboardConfiguration.scala
new file mode 100644
index 000000000..a3d38dc7a
--- /dev/null
+++ b/http/baker-http-dashboard/src-scala/main/scala/com/ing/baker/http/DashboardConfiguration.scala
@@ -0,0 +1,33 @@
+package com.ing.baker.http
+
+import com.typesafe.config.Config
+
+import scala.annotation.nowarn
+import scala.collection.immutable.Map
+import scala.collection.JavaConverters._
+
+case class DashboardConfiguration(enabled: Boolean,
+ applicationName: String,
+ clusterInformation: Map[String, String])
+
+object DashboardConfiguration {
+ @nowarn
+ def fromConfig(config: Config) : DashboardConfiguration = {
+ val dashboardConfig = config.getConfig("baker.dashboard")
+ val clusterInformation : Map[String, String] = {
+ if (dashboardConfig.hasPath("cluster-information"))
+ dashboardConfig
+ .getConfig("cluster-information")
+ .entrySet().asScala
+ .map(entry => (entry.getKey, entry.getValue.unwrapped().toString)).toMap
+ else Map.empty
+ }
+
+ DashboardConfiguration(
+ enabled = dashboardConfig.getBoolean("enabled"),
+ applicationName = dashboardConfig.getString("application-name"),
+ clusterInformation = clusterInformation
+ )
+ }
+}
+
diff --git a/http/baker-http-dashboard/src-scala/test/scala/com/ing/baker/http/DashboardAndConfigurationSpec.scala b/http/baker-http-dashboard/src-scala/test/scala/com/ing/baker/http/DashboardAndConfigurationSpec.scala
new file mode 100644
index 000000000..3db1b38ee
--- /dev/null
+++ b/http/baker-http-dashboard/src-scala/test/scala/com/ing/baker/http/DashboardAndConfigurationSpec.scala
@@ -0,0 +1,55 @@
+package com.ing.baker.http
+
+import org.scalatest.flatspec._
+import org.scalatest.matchers._
+
+import scala.io.Source
+import scala.util.Try
+
+
+class DashboardAndConfigurationSpec extends AnyFlatSpec with should.Matchers {
+
+ "The dashboard object" should "list the static files" in {
+ Dashboard.files.find(_ == "index.html") should not be empty
+ }
+
+ "The safe get resource url" should "return a valid resource" in {
+ val path = Dashboard.safeGetResourcePath("index.html")
+ path should not be empty
+ Try(Source.fromResource(path.get)).toOption should not be empty
+ }
+
+ "The versionJson" should "return a valid response" in {
+ val configuration = DashboardConfiguration(
+ enabled = true,
+ applicationName = "application name",
+ clusterInformation = Map(
+ "version1" -> "1.0",
+ "version2" -> "2.0"
+ )
+ )
+ Dashboard.dashboardConfigJson("/test/path", configuration).replace(" ", "") shouldEqual
+ """{
+ | "applicationName": "application name",
+ | "apiPath": "/test/path",
+ | "clusterInformation": {
+ | "version1": "1.0",
+ | "version2": "2.0"
+ | }
+ | }
+ |""".stripMargin.replace(" ", "")
+ }
+
+ "The dashboard object" should "match correct urls" in {
+ //TODO Revert this commit once scala-212 is no longer supported.
+ "".matches(Dashboard.indexPattern.regex) shouldBe true
+ "/".matches(Dashboard.indexPattern.regex) shouldBe true
+ "/recipes".matches(Dashboard.indexPattern.regex) shouldBe true
+ "/interactions".matches(Dashboard.indexPattern.regex) shouldBe true
+ "/instances".matches(Dashboard.indexPattern.regex) shouldBe true
+ "/instances/instance-id".matches(Dashboard.indexPattern.regex) shouldBe true
+ "/instanceand".matches(Dashboard.indexPattern.regex) shouldBe false
+ "/instance/".matches(Dashboard.indexPattern.regex) shouldBe false
+ }
+
+}
diff --git a/bakery/dashboard/src/app/app-routing.module.ts b/http/baker-http-dashboard/src/app/app-routing.module.ts
similarity index 100%
rename from bakery/dashboard/src/app/app-routing.module.ts
rename to http/baker-http-dashboard/src/app/app-routing.module.ts
diff --git a/bakery/dashboard/src/app/app.component.css b/http/baker-http-dashboard/src/app/app.component.css
similarity index 100%
rename from bakery/dashboard/src/app/app.component.css
rename to http/baker-http-dashboard/src/app/app.component.css
diff --git a/bakery/dashboard/src/app/app.component.html b/http/baker-http-dashboard/src/app/app.component.html
similarity index 100%
rename from bakery/dashboard/src/app/app.component.html
rename to http/baker-http-dashboard/src/app/app.component.html
diff --git a/bakery/dashboard/src/app/app.component.ts b/http/baker-http-dashboard/src/app/app.component.ts
similarity index 94%
rename from bakery/dashboard/src/app/app.component.ts
rename to http/baker-http-dashboard/src/app/app.component.ts
index 1dbfba483..80df3c651 100644
--- a/bakery/dashboard/src/app/app.component.ts
+++ b/http/baker-http-dashboard/src/app/app.component.ts
@@ -9,7 +9,7 @@ import {wasmFolder} from "@hpcc-js/wasm";
"templateUrl": "./app.component.html"
})
export class AppComponent implements OnDestroy, OnInit {
- title = AppSettingsService.settings.title;
+ title = AppSettingsService.settings.applicationName;
mobileQuery: MediaQueryList;
private readonly mobileQueryListener: () => void;
diff --git a/bakery/dashboard/src/app/app.module.ts b/http/baker-http-dashboard/src/app/app.module.ts
similarity index 100%
rename from bakery/dashboard/src/app/app.module.ts
rename to http/baker-http-dashboard/src/app/app.module.ts
diff --git a/bakery/dashboard/src/app/app.settings.ts b/http/baker-http-dashboard/src/app/app.settings.ts
similarity index 64%
rename from bakery/dashboard/src/app/app.settings.ts
rename to http/baker-http-dashboard/src/app/app.settings.ts
index c7cdd6a55..4b0630ee7 100644
--- a/bakery/dashboard/src/app/app.settings.ts
+++ b/http/baker-http-dashboard/src/app/app.settings.ts
@@ -1,13 +1,13 @@
import {HttpClient} from "@angular/common/http";
import {Injectable} from "@angular/core";
+import {Value} from "./baker-value.api";
-const SETTINGS_LOCATION = "assets/settings/settings.json";
+const SETTINGS_LOCATION = "/dashboard_config";
export interface AppSettings {
- apiUrl: string;
- title: string;
- bakeryVersion: string;
- stateVersion: string;
+ applicationName: string;
+ apiPath: string;
+ clusterInformation: { [key: string]: string };
}
@Injectable()
@@ -27,5 +27,11 @@ export class AppSettingsService {
}).
catch((response: any) => reject(Error(`Could not load file '${SETTINGS_LOCATION}': ${JSON.stringify(response)}`)));
});
+ //// For testing purposes:
+ // AppSettingsService.settings = {
+ // "applicationName": "Test",
+ // "apiPath": "/api/bakery",
+ // "clusterInformation": {}
+ // };
}
}
diff --git a/bakery/dashboard/src/app/baker-conversion.service.ts b/http/baker-http-dashboard/src/app/baker-conversion.service.ts
similarity index 100%
rename from bakery/dashboard/src/app/baker-conversion.service.ts
rename to http/baker-http-dashboard/src/app/baker-conversion.service.ts
diff --git a/bakery/dashboard/src/app/baker-types.api.ts b/http/baker-http-dashboard/src/app/baker-types.api.ts
similarity index 100%
rename from bakery/dashboard/src/app/baker-types.api.ts
rename to http/baker-http-dashboard/src/app/baker-types.api.ts
diff --git a/bakery/dashboard/src/app/baker-value.api.ts b/http/baker-http-dashboard/src/app/baker-value.api.ts
similarity index 100%
rename from bakery/dashboard/src/app/baker-value.api.ts
rename to http/baker-http-dashboard/src/app/baker-value.api.ts
diff --git a/bakery/dashboard/src/app/bakery.api.ts b/http/baker-http-dashboard/src/app/bakery.api.ts
similarity index 93%
rename from bakery/dashboard/src/app/bakery.api.ts
rename to http/baker-http-dashboard/src/app/bakery.api.ts
index 343704138..faee0e345 100644
--- a/bakery/dashboard/src/app/bakery.api.ts
+++ b/http/baker-http-dashboard/src/app/bakery.api.ts
@@ -9,11 +9,17 @@ export interface Recipe {
errors: string[];
}
+export interface RecipeBodyCompiledRecipe {
+ name: string;
+ recipeId: string;
+ validationErrors: string[];
+}
+
export interface RecipeBody {
- compiledRecipe: Recipe;
+ compiledRecipe: RecipeBodyCompiledRecipe;
recipeCreatedTime: number;
- validate: boolean;
errors: string[];
+ validate: boolean;
}
export interface Recipes {
diff --git a/bakery/dashboard/src/app/bakery.service.ts b/http/baker-http-dashboard/src/app/bakery.service.ts
similarity index 97%
rename from bakery/dashboard/src/app/bakery.service.ts
rename to http/baker-http-dashboard/src/app/bakery.service.ts
index b5f4ef49e..a378d71af 100644
--- a/bakery/dashboard/src/app/bakery.service.ts
+++ b/http/baker-http-dashboard/src/app/bakery.service.ts
@@ -23,7 +23,7 @@ import {Injectable} from "@angular/core";
@Injectable({"providedIn": "root"})
export class BakeryService {
- private baseUrl = AppSettingsService.settings.apiUrl;
+ private baseUrl = AppSettingsService.settings.apiPath;
httpOptions = {
"headers": new HttpHeaders({"Content-Type": "application/json"})
@@ -40,7 +40,7 @@ export class BakeryService {
pipe(map(recipes => Object.values(recipes.body)
.map(response => {
const row: Recipe = {
- "errors": response.compiledRecipe.errors,
+ "errors": response.errors,
"name": response.compiledRecipe.name,
"recipeCreatedTime": response.recipeCreatedTime,
"recipeId": response.compiledRecipe.recipeId,
diff --git a/bakery/dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.html b/http/baker-http-dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.html
similarity index 100%
rename from bakery/dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.html
rename to http/baker-http-dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.html
diff --git a/bakery/dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.ts b/http/baker-http-dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.ts
rename to http/baker-http-dashboard/src/app/generic/visualize-recipe/visualize-recipe.component.ts
diff --git a/bakery/dashboard/src/app/generic/visualize-recipe/visualize-recipe.scss b/http/baker-http-dashboard/src/app/generic/visualize-recipe/visualize-recipe.scss
similarity index 100%
rename from bakery/dashboard/src/app/generic/visualize-recipe/visualize-recipe.scss
rename to http/baker-http-dashboard/src/app/generic/visualize-recipe/visualize-recipe.scss
diff --git a/http/baker-http-dashboard/src/app/home/home.component.html b/http/baker-http-dashboard/src/app/home/home.component.html
new file mode 100644
index 000000000..a7360efa6
--- /dev/null
+++ b/http/baker-http-dashboard/src/app/home/home.component.html
@@ -0,0 +1,5 @@
+
+
+ {{clusterInformation}}
+
+
diff --git a/bakery/dashboard/src/app/home/home.component.spec.ts b/http/baker-http-dashboard/src/app/home/home.component.spec.ts
similarity index 100%
rename from bakery/dashboard/src/app/home/home.component.spec.ts
rename to http/baker-http-dashboard/src/app/home/home.component.spec.ts
diff --git a/bakery/dashboard/src/app/home/home.component.ts b/http/baker-http-dashboard/src/app/home/home.component.ts
similarity index 55%
rename from bakery/dashboard/src/app/home/home.component.ts
rename to http/baker-http-dashboard/src/app/home/home.component.ts
index 41d094834..5b28af886 100644
--- a/bakery/dashboard/src/app/home/home.component.ts
+++ b/http/baker-http-dashboard/src/app/home/home.component.ts
@@ -1,4 +1,4 @@
-import {Component, OnInit, Renderer2} from "@angular/core";
+import {Component, OnInit} from "@angular/core";
import {AppSettingsService} from "../app.settings";
/** @title Bakery DashboardComponent */
@@ -9,15 +9,13 @@ import {AppSettingsService} from "../app.settings";
})
export class HomeComponent implements OnInit {
- bakeryVersion: string;
- stateVersion: string;
+ clusterInformation: string;
constructor () {
}
ngOnInit (): void {
- this.bakeryVersion = AppSettingsService.settings.bakeryVersion;
- this.stateVersion = AppSettingsService.settings.stateVersion;
+ this.clusterInformation = JSON.stringify(AppSettingsService.settings.clusterInformation);
}
}
diff --git a/bakery/dashboard/src/app/home/home.css b/http/baker-http-dashboard/src/app/home/home.css
similarity index 100%
rename from bakery/dashboard/src/app/home/home.css
rename to http/baker-http-dashboard/src/app/home/home.css
diff --git a/bakery/dashboard/src/app/instances/instances.component.html b/http/baker-http-dashboard/src/app/instances/instances.component.html
similarity index 100%
rename from bakery/dashboard/src/app/instances/instances.component.html
rename to http/baker-http-dashboard/src/app/instances/instances.component.html
diff --git a/bakery/dashboard/src/app/instances/instances.component.spec.ts b/http/baker-http-dashboard/src/app/instances/instances.component.spec.ts
similarity index 100%
rename from bakery/dashboard/src/app/instances/instances.component.spec.ts
rename to http/baker-http-dashboard/src/app/instances/instances.component.spec.ts
diff --git a/bakery/dashboard/src/app/instances/instances.component.ts b/http/baker-http-dashboard/src/app/instances/instances.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/instances/instances.component.ts
rename to http/baker-http-dashboard/src/app/instances/instances.component.ts
diff --git a/bakery/dashboard/src/app/instances/instances.css b/http/baker-http-dashboard/src/app/instances/instances.css
similarity index 100%
rename from bakery/dashboard/src/app/instances/instances.css
rename to http/baker-http-dashboard/src/app/instances/instances.css
diff --git a/bakery/dashboard/src/app/interactions/interaction-definition/interaction-definition.component.html b/http/baker-http-dashboard/src/app/interactions/interaction-definition/interaction-definition.component.html
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interaction-definition/interaction-definition.component.html
rename to http/baker-http-dashboard/src/app/interactions/interaction-definition/interaction-definition.component.html
diff --git a/bakery/dashboard/src/app/interactions/interaction-definition/interaction-definition.component.scss b/http/baker-http-dashboard/src/app/interactions/interaction-definition/interaction-definition.component.scss
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interaction-definition/interaction-definition.component.scss
rename to http/baker-http-dashboard/src/app/interactions/interaction-definition/interaction-definition.component.scss
diff --git a/bakery/dashboard/src/app/interactions/interaction-definition/interaction-definition.component.ts b/http/baker-http-dashboard/src/app/interactions/interaction-definition/interaction-definition.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interaction-definition/interaction-definition.component.ts
rename to http/baker-http-dashboard/src/app/interactions/interaction-definition/interaction-definition.component.ts
diff --git a/bakery/dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.html b/http/baker-http-dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.html
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.html
rename to http/baker-http-dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.html
diff --git a/bakery/dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.scss b/http/baker-http-dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.scss
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.scss
rename to http/baker-http-dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.scss
diff --git a/bakery/dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.ts b/http/baker-http-dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.ts
rename to http/baker-http-dashboard/src/app/interactions/interaction-manual-test/interaction-manual-test.component.ts
diff --git a/bakery/dashboard/src/app/interactions/interactions.component.html b/http/baker-http-dashboard/src/app/interactions/interactions.component.html
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interactions.component.html
rename to http/baker-http-dashboard/src/app/interactions/interactions.component.html
diff --git a/bakery/dashboard/src/app/interactions/interactions.component.spec.ts b/http/baker-http-dashboard/src/app/interactions/interactions.component.spec.ts
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interactions.component.spec.ts
rename to http/baker-http-dashboard/src/app/interactions/interactions.component.spec.ts
diff --git a/bakery/dashboard/src/app/interactions/interactions.component.ts b/http/baker-http-dashboard/src/app/interactions/interactions.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interactions.component.ts
rename to http/baker-http-dashboard/src/app/interactions/interactions.component.ts
diff --git a/bakery/dashboard/src/app/interactions/interactions.css b/http/baker-http-dashboard/src/app/interactions/interactions.css
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interactions.css
rename to http/baker-http-dashboard/src/app/interactions/interactions.css
diff --git a/bakery/dashboard/src/app/interactions/interactions.scss b/http/baker-http-dashboard/src/app/interactions/interactions.scss
similarity index 100%
rename from bakery/dashboard/src/app/interactions/interactions.scss
rename to http/baker-http-dashboard/src/app/interactions/interactions.scss
diff --git a/bakery/dashboard/src/app/notfound/notfound.component.html b/http/baker-http-dashboard/src/app/notfound/notfound.component.html
similarity index 100%
rename from bakery/dashboard/src/app/notfound/notfound.component.html
rename to http/baker-http-dashboard/src/app/notfound/notfound.component.html
diff --git a/bakery/dashboard/src/app/notfound/notfound.component.ts b/http/baker-http-dashboard/src/app/notfound/notfound.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/notfound/notfound.component.ts
rename to http/baker-http-dashboard/src/app/notfound/notfound.component.ts
diff --git a/bakery/dashboard/src/app/notfound/notfound.css b/http/baker-http-dashboard/src/app/notfound/notfound.css
similarity index 100%
rename from bakery/dashboard/src/app/notfound/notfound.css
rename to http/baker-http-dashboard/src/app/notfound/notfound.css
diff --git a/bakery/dashboard/src/app/recipes/recipes.component.html b/http/baker-http-dashboard/src/app/recipes/recipes.component.html
similarity index 100%
rename from bakery/dashboard/src/app/recipes/recipes.component.html
rename to http/baker-http-dashboard/src/app/recipes/recipes.component.html
diff --git a/bakery/dashboard/src/app/recipes/recipes.component.spec.ts b/http/baker-http-dashboard/src/app/recipes/recipes.component.spec.ts
similarity index 100%
rename from bakery/dashboard/src/app/recipes/recipes.component.spec.ts
rename to http/baker-http-dashboard/src/app/recipes/recipes.component.spec.ts
diff --git a/bakery/dashboard/src/app/recipes/recipes.component.ts b/http/baker-http-dashboard/src/app/recipes/recipes.component.ts
similarity index 100%
rename from bakery/dashboard/src/app/recipes/recipes.component.ts
rename to http/baker-http-dashboard/src/app/recipes/recipes.component.ts
diff --git a/bakery/dashboard/src/app/recipes/recipes.scss b/http/baker-http-dashboard/src/app/recipes/recipes.scss
similarity index 100%
rename from bakery/dashboard/src/app/recipes/recipes.scss
rename to http/baker-http-dashboard/src/app/recipes/recipes.scss
diff --git a/bakery/dashboard/src/assets/.gitkeep b/http/baker-http-dashboard/src/assets/.gitkeep
similarity index 100%
rename from bakery/dashboard/src/assets/.gitkeep
rename to http/baker-http-dashboard/src/assets/.gitkeep
diff --git a/bakery/dashboard/src/assets/@hpcc-js/wasm/dist/graphvizlib.wasm b/http/baker-http-dashboard/src/assets/@hpcc-js/wasm/dist/graphvizlib.wasm
similarity index 100%
rename from bakery/dashboard/src/assets/@hpcc-js/wasm/dist/graphvizlib.wasm
rename to http/baker-http-dashboard/src/assets/@hpcc-js/wasm/dist/graphvizlib.wasm
diff --git a/bakery/dashboard/src/custom-theme.scss b/http/baker-http-dashboard/src/custom-theme.scss
similarity index 100%
rename from bakery/dashboard/src/custom-theme.scss
rename to http/baker-http-dashboard/src/custom-theme.scss
diff --git a/bakery/dashboard/src/environments/environment.prod.ts b/http/baker-http-dashboard/src/environments/environment.prod.ts
similarity index 100%
rename from bakery/dashboard/src/environments/environment.prod.ts
rename to http/baker-http-dashboard/src/environments/environment.prod.ts
diff --git a/bakery/dashboard/src/environments/environment.ts b/http/baker-http-dashboard/src/environments/environment.ts
similarity index 100%
rename from bakery/dashboard/src/environments/environment.ts
rename to http/baker-http-dashboard/src/environments/environment.ts
diff --git a/bakery/dashboard/src/favicon.ico b/http/baker-http-dashboard/src/favicon.ico
similarity index 100%
rename from bakery/dashboard/src/favicon.ico
rename to http/baker-http-dashboard/src/favicon.ico
diff --git a/bakery/dashboard/src/index.html b/http/baker-http-dashboard/src/index.html
similarity index 100%
rename from bakery/dashboard/src/index.html
rename to http/baker-http-dashboard/src/index.html
diff --git a/bakery/dashboard/src/main.ts b/http/baker-http-dashboard/src/main.ts
similarity index 100%
rename from bakery/dashboard/src/main.ts
rename to http/baker-http-dashboard/src/main.ts
diff --git a/bakery/dashboard/src/polyfills.ts b/http/baker-http-dashboard/src/polyfills.ts
similarity index 100%
rename from bakery/dashboard/src/polyfills.ts
rename to http/baker-http-dashboard/src/polyfills.ts
diff --git a/http/baker-http-dashboard/src/proxy.conf.json b/http/baker-http-dashboard/src/proxy.conf.json
new file mode 100644
index 000000000..154f50676
--- /dev/null
+++ b/http/baker-http-dashboard/src/proxy.conf.json
@@ -0,0 +1,10 @@
+{
+ // Will not work against https if self-signed. Use it to test against a non-https (e.g. localhost) baker-http-server.
+ // Sometimes failures will be cached. It can be helpful to test it in incognito mode in the browser.
+ "/api/bakery": {
+ "target": "http://localhost:8080",
+ "secure": false,
+ "logLevel": "debug",
+ "changeOrigin": true
+ }
+}
diff --git a/bakery/dashboard/src/styles.scss b/http/baker-http-dashboard/src/styles.scss
similarity index 100%
rename from bakery/dashboard/src/styles.scss
rename to http/baker-http-dashboard/src/styles.scss
diff --git a/bakery/dashboard/src/test.ts b/http/baker-http-dashboard/src/test.ts
similarity index 100%
rename from bakery/dashboard/src/test.ts
rename to http/baker-http-dashboard/src/test.ts
diff --git a/bakery/dashboard/tsconfig.app.json b/http/baker-http-dashboard/tsconfig.app.json
similarity index 100%
rename from bakery/dashboard/tsconfig.app.json
rename to http/baker-http-dashboard/tsconfig.app.json
diff --git a/bakery/dashboard/tsconfig.json b/http/baker-http-dashboard/tsconfig.json
similarity index 100%
rename from bakery/dashboard/tsconfig.json
rename to http/baker-http-dashboard/tsconfig.json
diff --git a/bakery/dashboard/tsconfig.spec.json b/http/baker-http-dashboard/tsconfig.spec.json
similarity index 100%
rename from bakery/dashboard/tsconfig.spec.json
rename to http/baker-http-dashboard/tsconfig.spec.json
diff --git a/http/baker-http-server/src/main/resources/reference.conf b/http/baker-http-server/src/main/resources/reference.conf
new file mode 100644
index 000000000..ec1f8296d
--- /dev/null
+++ b/http/baker-http-server/src/main/resources/reference.conf
@@ -0,0 +1,6 @@
+baker {
+ api-host = "0.0.0.0"
+ api-port = 8080
+ api-url-prefix = "/api/bakery"
+ api-logging-enabled = false
+}
diff --git a/bakery/state/src/main/scala/com/ing/bakery/baker/RecipeLoader.scala b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/common/RecipeLoader.scala
similarity index 91%
rename from bakery/state/src/main/scala/com/ing/bakery/baker/RecipeLoader.scala
rename to http/baker-http-server/src/main/scala/com/ing/baker/http/server/common/RecipeLoader.scala
index 537221ae4..ef0be97ab 100644
--- a/bakery/state/src/main/scala/com/ing/bakery/baker/RecipeLoader.scala
+++ b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/common/RecipeLoader.scala
@@ -1,4 +1,4 @@
-package com.ing.bakery.baker
+package com.ing.baker.http.server.common
import cats.effect.{ContextShift, IO, Timer}
import cats.implicits._
@@ -20,9 +20,9 @@ import scala.util.Try
object RecipeLoader extends LazyLogging {
- def pollRecipesUpdates(path: String, bakery: Bakery, duration: FiniteDuration)
+ def pollRecipesUpdates(path: String, baker: Baker, duration: FiniteDuration)
(implicit timer: Timer[IO], cs: ContextShift[IO]): IO[Unit] = {
- def pollRecipes: IO[Unit] = loadRecipesIntoBaker(path, bakery.baker) >> IO.sleep(duration) >> IO.defer(pollRecipes)
+ def pollRecipes: IO[Unit] = loadRecipesIntoBaker(path, baker) >> IO.sleep(duration) >> IO.defer(pollRecipes)
pollRecipes
}
@@ -30,7 +30,7 @@ object RecipeLoader extends LazyLogging {
def loadRecipesIntoBaker(path: String, baker: Baker)(implicit cs: ContextShift[IO]): IO[Unit] =
for {
recipes <- RecipeLoader.loadRecipes(path)
- _ <- recipes.traverse { record =>
+ _ <- recipes.traverse { record =>
IO.fromFuture(IO(baker.addRecipe(record)))
}
} yield ()
@@ -51,7 +51,7 @@ object RecipeLoader extends LazyLogging {
bytes
}
- private[baker] def loadRecipes(path: String): IO[List[RecipeRecord]] = {
+ def loadRecipes(path: String): IO[List[RecipeRecord]] = {
def recipeFiles(path: String): IO[List[File]] = IO {
val d = new File(path)
diff --git a/http/baker-http-server/src/main/scala/com/ing/baker/http/server/javadsl/BakerWithHttpResponse.scala b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/javadsl/BakerWithHttpResponse.scala
new file mode 100644
index 000000000..f2eb1eb4f
--- /dev/null
+++ b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/javadsl/BakerWithHttpResponse.scala
@@ -0,0 +1,110 @@
+package com.ing.baker.http.server.javadsl
+
+import com.ing.baker.http.server.common.RecipeLoader
+import com.ing.baker.runtime.common.BakerException
+import com.ing.baker.runtime.scaladsl.{Baker, BakerResult, EncodedRecipe, EventInstance}
+import com.ing.baker.runtime.serialization.JsonDecoders._
+import com.ing.baker.runtime.serialization.JsonEncoders._
+import com.typesafe.scalalogging.LazyLogging
+import io.circe.Encoder
+import io.circe.generic.auto._
+import io.circe.parser.parse
+
+import java.nio.charset.Charset
+import java.util.concurrent.{CompletableFuture => JFuture}
+import java.util.{Optional, UUID}
+import scala.compat.java8.FutureConverters.FutureOps
+import scala.concurrent.{ExecutionContext, Future}
+
+/**
+ * A wrapper around baker which calls the specified baker instance, and returns the BakerResult according to the bakery protocol.
+ * Useful when making your own controller.
+ *
+ * @param baker baker methods to wrap
+ * @param ec execution context to use
+ */
+class BakerWithHttpResponse(val baker: Baker, ec: ExecutionContext) extends LazyLogging {
+ implicit val executionContext: ExecutionContext = ec
+
+ def appGetAllInteractions: JFuture[String] = baker.getAllInteractions.toBakerResult
+
+ def appGetInteraction(interactionName: String): JFuture[String] = baker.getInteraction(interactionName).toBakerResult
+
+ def appAddRecipe(recipe: String): JFuture[String] = {
+ (for {
+ json <- parse(recipe).toOption
+ encodedRecipe <- json.as[EncodedRecipe].toOption
+ } yield RecipeLoader.fromBytes(encodedRecipe.base64.getBytes(Charset.forName("UTF-8"))).unsafeToFuture())
+ .map(_.flatMap(recipe => baker.addRecipe(recipe, validate = false).toBakerResultScalaFuture))
+ .getOrElse(Future.failed(new IllegalStateException("Error adding recipe")))
+ }.toJava.toCompletableFuture
+
+ def appGetRecipe(recipeId: String): JFuture[String] = baker.getRecipe(recipeId).toBakerResult
+
+ def appGetAllRecipes: JFuture[String] = baker.getAllRecipes.toBakerResult
+
+ def appGetVisualRecipe(recipeId: String): JFuture[String] = baker.getRecipeVisual(recipeId).toBakerResult
+
+ def bake(recipeId: String, recipeInstanceId: String): JFuture[String] = baker.bake(recipeId, recipeInstanceId).toBakerResult
+
+ /**
+ * Do calls for a specific instance.
+ */
+ def instance(recipeInstanceId: String) : InstanceResponseMapper = new InstanceResponseMapper(recipeInstanceId)
+
+ class InstanceResponseMapper(recipeInstanceId: String) {
+ def get(): JFuture[String] = baker.getRecipeInstanceState(recipeInstanceId).toBakerResult
+
+ def getEvents: JFuture[String] = baker.getEvents(recipeInstanceId).toBakerResult
+
+ def getIngredients: JFuture[String] = baker.getIngredients(recipeInstanceId).toBakerResult
+
+ def getVisual: JFuture[String] = baker.getVisualState(recipeInstanceId).toBakerResult
+
+ def fireAndResolveWhenReceived(eventJson: String, maybeCorrelationId: Optional[String]): JFuture[String] =
+ parseEventAndExecute(eventJson, baker.fireEventAndResolveWhenReceived(recipeInstanceId, _, toOption(maybeCorrelationId)))
+
+ def fireAndResolveWhenCompleted(eventJson: String, maybeCorrelationId: Optional[String]): JFuture[String] =
+ parseEventAndExecute(eventJson, baker.fireEventAndResolveWhenCompleted(recipeInstanceId, _, toOption(maybeCorrelationId)))
+
+ def fireAndResolveOnEvent(eventJson: String, event: String, maybeCorrelationId: Optional[String]): JFuture[String] =
+ parseEventAndExecute(eventJson, baker.fireEventAndResolveOnEvent(recipeInstanceId, _, event, toOption(maybeCorrelationId)))
+
+ def retryInteraction(interactionName: String): JFuture[String] =
+ baker.retryInteraction(recipeInstanceId, interactionName).toBakerResult
+
+ def stopRetryingInteraction(interactionName: String): JFuture[String] =
+ baker.stopRetryingInteraction(recipeInstanceId, interactionName).toBakerResult
+
+ def resolveInteraction(interactionName: String, eventJson: String): JFuture[String] =
+ parseEventAndExecute(eventJson, baker.resolveInteraction(recipeInstanceId, interactionName, _))
+ }
+
+ private def toOption[T](opt: Optional[T]): Option[T] = if (opt.isPresent) Some(opt.get()) else None
+
+ private def parseEventAndExecute[A](eventJson: String, f: EventInstance => Future[A])(implicit encoder: Encoder[A]): JFuture[String] = (for {
+ json <- parse(eventJson)
+ eventInstance <- json.as[EventInstance]
+ } yield {
+ f(eventInstance).toBakerResultScalaFuture
+ }).getOrElse(Future.failed(new IllegalArgumentException("Can't process event"))).toJava.toCompletableFuture
+
+ private implicit class BakerResultHelperJavaFuture[A](f: => Future[A])(implicit encoder: Encoder[A]) {
+ def toBakerResult: JFuture[String] = f.toBakerResultScalaFuture.toJava.toCompletableFuture
+ }
+
+ private implicit class BakerResultHelperScalaFuture[A](f: => Future[A])(implicit encoder: Encoder[A]) {
+ def toBakerResultScalaFuture(implicit encoder: Encoder[A]): Future[String] = {
+ f.map {
+ case () => BakerResult.Ack
+ case a => BakerResult(a)
+ }.recover {
+ case e: BakerException => BakerResult(e)
+ case e: Throwable =>
+ val errorId = UUID.randomUUID().toString
+ logger.error(s"Unexpected exception happened when calling Baker (id='$errorId').", e)
+ BakerResult(BakerException.UnexpectedException(errorId))
+ }.map(bakerResultEncoder.apply(_).noSpaces)
+ }
+ }
+}
diff --git a/http/baker-http-server/src/main/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServer.scala b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServer.scala
new file mode 100644
index 000000000..22fc6e4b1
--- /dev/null
+++ b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServer.scala
@@ -0,0 +1,280 @@
+package com.ing.baker.http.server.scaladsl
+
+import cats.data.OptionT
+import cats.effect.{Blocker, ContextShift, IO, Resource, Sync, Timer}
+import cats.implicits._
+import com.ing.baker.http.{Dashboard, DashboardConfiguration}
+import com.ing.baker.http.server.common.RecipeLoader
+import com.ing.baker.runtime.common.BakerException
+import com.ing.baker.runtime.scaladsl.{Baker, BakerResult, EncodedRecipe, EventInstance}
+import com.ing.baker.runtime.javadsl.{Baker => JBaker}
+import com.ing.baker.runtime.serialization.InteractionExecution
+import com.ing.baker.runtime.serialization.InteractionExecutionJsonCodecs._
+import com.ing.baker.runtime.serialization.JsonDecoders._
+import com.ing.baker.runtime.serialization.JsonEncoders._
+import com.typesafe.config.{Config, ConfigFactory}
+import com.typesafe.scalalogging.LazyLogging
+import io.circe._
+import io.circe.generic.auto._
+import io.prometheus.client.CollectorRegistry
+import org.http4s._
+import org.http4s.circe._
+import org.http4s.dsl.io._
+import org.http4s.headers.{`Content-Length`, `Content-Type`}
+import org.http4s.implicits._
+import org.http4s.metrics.MetricsOps
+import org.http4s.metrics.prometheus.Prometheus
+import org.http4s.server.blaze.BlazeServerBuilder
+import org.http4s.server.middleware.{CORS, Logger, Metrics}
+import org.http4s.server.{Router, Server}
+import org.slf4j.LoggerFactory
+
+import java.io.Closeable
+import java.net.InetSocketAddress
+import java.nio.charset.Charset
+import java.util.concurrent.CompletableFuture
+import scala.compat.java8.FutureConverters
+import scala.concurrent.duration.DurationInt
+import scala.concurrent.{ExecutionContext, Future}
+
+object Http4sBakerServer {
+
+ def resource(baker: Baker, ec: ExecutionContext, hostname: InetSocketAddress, apiUrlPrefix: String,
+ dashboardConfiguration: DashboardConfiguration, loggingEnabled: Boolean)
+ (implicit sync: Sync[IO], cs: ContextShift[IO], timer: Timer[IO]): Resource[IO, Server[IO]] = {
+
+
+ val apiLoggingAction: Option[String => IO[Unit]] = if (loggingEnabled) {
+ val apiLogger = LoggerFactory.getLogger("API")
+ Some(s => IO(apiLogger.info(s)))
+ } else None
+
+ for {
+ metrics <- Prometheus.metricsOps[IO](CollectorRegistry.defaultRegistry, "http_api")
+ blocker <- Blocker[IO]
+ server <- BlazeServerBuilder[IO](ec)
+ .bindSocketAddress(hostname)
+ .withHttpApp(
+ CORS.policy
+ .withAllowOriginAll
+ .withAllowCredentials(true)
+ .withMaxAge(1.day)(
+ Logger.httpApp(
+ logHeaders = loggingEnabled,
+ logBody = loggingEnabled,
+ logAction = apiLoggingAction)(
+ routes(baker, apiUrlPrefix, metrics, dashboardConfiguration, blocker).orNotFound)))
+ .resource
+ } yield server
+ }
+
+ def resource(baker: Baker,
+ http4sBakerServerConfiguration: Http4sBakerServerConfiguration,
+ dashboardConfiguration: DashboardConfiguration,
+ ec: ExecutionContext = ExecutionContext.global)
+ (implicit sync: Sync[IO], cs: ContextShift[IO], timer: Timer[IO]): Resource[IO, Server[IO]] =
+ resource(baker, ec,
+ hostname = InetSocketAddress.createUnresolved(http4sBakerServerConfiguration.apiHost, http4sBakerServerConfiguration.apiPort),
+ apiUrlPrefix = http4sBakerServerConfiguration.apiUrlPrefix,
+ dashboardConfiguration = dashboardConfiguration,
+ loggingEnabled = http4sBakerServerConfiguration.loggingEnabled
+ )
+
+ def java(baker: JBaker,
+ http4sBakerServerConfiguration: Http4sBakerServerConfiguration,
+ dashboardConfiguration: DashboardConfiguration,
+ ): CompletableFuture[ClosableBakerServer] = {
+ implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
+ implicit val contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
+ val serverStarted = resource(baker.getScalaBaker, http4sBakerServerConfiguration, dashboardConfiguration)
+ .allocated
+ .unsafeToFuture()
+ .map {
+ case (server: Server[IO], closeEffect: IO[Unit]) => new ClosableBakerServer(server, closeEffect)
+ }(ExecutionContext.global)
+ FutureConverters.toJava(serverStarted).toCompletableFuture
+ }
+
+ def java(baker: JBaker): CompletableFuture[ClosableBakerServer] = {
+ val config : Config = ConfigFactory.load()
+ java(baker, Http4sBakerServerConfiguration.fromConfig(config), DashboardConfiguration.fromConfig(config))
+ }
+
+ class ClosableBakerServer(val server : Server[IO], closeEffect : IO[Unit]) extends Closeable {
+ override def close(): Unit = closeEffect.unsafeRunSync()
+ }
+ def routes(baker: Baker, apiUrlPrefix: String, metrics: MetricsOps[IO],
+ dashboardConfiguration: DashboardConfiguration, blocker: Blocker)
+ (implicit sync: Sync[IO], cs: ContextShift[IO], timer: Timer[IO]): HttpRoutes[IO] = {
+ val dashboardRoutesOrEmpty: HttpRoutes[IO] =
+ if (dashboardConfiguration.enabled) dashboardRoutes(apiUrlPrefix, dashboardConfiguration, blocker)
+ else HttpRoutes.empty
+
+ new Http4sBakerServer(baker).routesWithPrefixAndMetrics(apiUrlPrefix, metrics) <+> dashboardRoutesOrEmpty
+ }
+
+
+ private def dashboardRoutes(apiUrlPrefix: String, dashboardConfiguration: DashboardConfiguration, blocker: Blocker)
+ (implicit sync: Sync[IO], cs: ContextShift[IO]): HttpRoutes[IO] =
+ HttpRoutes.of[IO] {
+ case GET -> Root / "dashboard_config" =>
+ val bodyText = Dashboard.dashboardConfigJson(apiUrlPrefix, dashboardConfiguration)
+ IO(Response[IO](
+ status = Ok,
+ body = fs2.Stream(bodyText).through(fs2.text.utf8Encode),
+ headers = Headers(
+ `Content-Type`(MediaType.text.plain, org.http4s.Charset.`UTF-8`),
+ `Content-Length`.unsafeFromLong(bodyText.length)
+ )
+ ))
+ //TODO: Change to Dashboard.indexPattern.matches(req.pathInfo) once support for scala_2.12 is removed.
+ case req if req.method == GET && req.pathInfo.matches(Dashboard.indexPattern.regex) => dashboardFile(req, blocker, "index.html").getOrElseF(NotFound())
+ case req if req.method == GET && Dashboard.files.contains(req.pathInfo.substring(1)) =>
+ dashboardFile(req, blocker, req.pathInfo.substring(1)).getOrElseF(NotFound())
+ }
+
+ private def dashboardFile(request: Request[IO], blocker: Blocker, filename: String)
+ (implicit sync: Sync[IO], cs: ContextShift[IO]): OptionT[IO, Response[IO]] = {
+ OptionT.fromOption(Dashboard.safeGetResourcePath(filename))(sync)
+ .flatMap(resourcePath => StaticFile.fromResource(resourcePath, blocker, Some(request)))
+ }
+}
+
+final class Http4sBakerServer private(baker: Baker)(implicit cs: ContextShift[IO]) extends LazyLogging {
+
+ object CorrelationId extends OptionalQueryParamDecoderMatcher[String]("correlationId")
+
+ private class RegExpValidator(regexp: String) {
+ def unapply(str: String): Option[String] = if (str.matches(regexp)) Some(str) else None
+ }
+
+ private object RecipeId extends RegExpValidator("[A-Za-z0-9]+")
+
+ private object RecipeInstanceId extends RegExpValidator("[A-Za-z0-9-]+")
+
+ private object InteractionName extends RegExpValidator("[A-Za-z0-9_]+")
+
+ implicit val recipeDecoder: EntityDecoder[IO, EncodedRecipe] = jsonOf[IO, EncodedRecipe]
+
+ implicit val eventInstanceDecoder: EntityDecoder[IO, EventInstance] = jsonOf[IO, EventInstance]
+ implicit val interactionExecutionRequestDecoder: EntityDecoder[IO, InteractionExecution.ExecutionRequest] = jsonOf[IO, InteractionExecution.ExecutionRequest]
+ implicit val bakerResultEntityEncoder: EntityEncoder[IO, BakerResult] = jsonEncoderOf[IO, BakerResult]
+
+ def routesWithPrefixAndMetrics(apiUrlPrefix: String, metrics: MetricsOps[IO])
+ (implicit timer: Timer[IO]): HttpRoutes[IO] =
+ Router(
+ apiUrlPrefix -> Metrics[IO](metrics, classifierF = metricsClassifier(apiUrlPrefix))(routes),
+ )
+
+ def routes: HttpRoutes[IO] = app <+> instance
+
+ private def app: HttpRoutes[IO] = Router("/app" ->
+ HttpRoutes.of[IO] {
+ case GET -> Root / "health" => Ok()
+
+ case GET -> Root / "interactions" => baker.getAllInteractions.toBakerResultResponseIO
+
+ case GET -> Root / "interactions" / InteractionName(name) => baker.getInteraction(name).toBakerResultResponseIO
+
+ case req@POST -> Root / "interactions" / "execute" =>
+ for {
+ executionRequest <- req.as[InteractionExecution.ExecutionRequest]
+ result <-
+ IO.fromFuture(IO(baker.executeSingleInteraction(executionRequest.id, executionRequest.ingredients)))
+ .map(_.toSerializationInteractionExecutionResult)
+ .toBakerResultResponseIO
+ } yield result
+
+ case req@POST -> Root / "recipes" =>
+ for {
+ encodedRecipe <- req.as[EncodedRecipe]
+ recipe <- RecipeLoader.fromBytes(encodedRecipe.base64.getBytes(Charset.forName("UTF-8")))
+ result <- baker.addRecipe(recipe, validate = true).toBakerResultResponseIO
+ } yield result
+
+ case GET -> Root / "recipes" => baker.getAllRecipes.toBakerResultResponseIO
+
+ case GET -> Root / "recipes" / RecipeId(recipeId) => baker.getRecipe(recipeId).toBakerResultResponseIO
+
+ case GET -> Root / "recipes" / RecipeId(recipeId) / "visual" => baker.getRecipeVisual(recipeId).toBakerResultResponseIO
+ })
+
+ private def instance: HttpRoutes[IO] = Router("/instances" -> HttpRoutes.of[IO] {
+
+ case GET -> Root => baker.getAllRecipeInstancesMetadata.toBakerResultResponseIO
+
+ case GET -> Root / RecipeInstanceId(recipeInstanceId) => baker.getRecipeInstanceState(recipeInstanceId).toBakerResultResponseIO
+
+ case GET -> Root / RecipeInstanceId(recipeInstanceId) / "events" => baker.getEvents(recipeInstanceId).toBakerResultResponseIO
+
+ case GET -> Root / RecipeInstanceId(recipeInstanceId) / "ingredients" => baker.getIngredients(recipeInstanceId).toBakerResultResponseIO
+
+ case GET -> Root / RecipeInstanceId(recipeInstanceId) / "visual" => baker.getVisualState(recipeInstanceId).toBakerResultResponseIO
+
+ case POST -> Root / RecipeInstanceId(recipeInstanceId) / "bake" / RecipeId(recipeId) => baker.bake(recipeId, recipeInstanceId).toBakerResultResponseIO
+
+ case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "fire-and-resolve-when-received" :? CorrelationId(maybeCorrelationId) =>
+ for {
+ event <- req.as[EventInstance]
+ result <- baker.fireEventAndResolveWhenReceived(recipeInstanceId, event, maybeCorrelationId).toBakerResultResponseIO
+ } yield result
+
+ case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "fire-and-resolve-when-completed" :? CorrelationId(maybeCorrelationId) =>
+ for {
+ event <- req.as[EventInstance]
+ result <- baker.fireEventAndResolveWhenCompleted(recipeInstanceId, event, maybeCorrelationId).toBakerResultResponseIO
+ } yield result
+
+ case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "fire-and-resolve-on-event" / onEvent :? CorrelationId(maybeCorrelationId) =>
+ for {
+ event <- req.as[EventInstance]
+ result <- baker.fireEventAndResolveOnEvent(recipeInstanceId, event, onEvent, maybeCorrelationId).toBakerResultResponseIO
+ } yield result
+
+ case POST -> Root / RecipeInstanceId(recipeInstanceId) / "interaction" / InteractionName(interactionName) / "retry" =>
+ for {
+ result <- baker.retryInteraction(recipeInstanceId, interactionName).toBakerResultResponseIO
+ } yield result
+
+ case POST -> Root / RecipeInstanceId(recipeInstanceId) / "interaction" / InteractionName(interactionName) / "stop-retrying" =>
+ for {
+ result <- baker.stopRetryingInteraction(recipeInstanceId, interactionName).toBakerResultResponseIO
+ } yield result
+
+ case req@POST -> Root / RecipeInstanceId(recipeInstanceId) / "interaction" / InteractionName(interactionName) / "resolve" =>
+ for {
+ event <- req.as[EventInstance]
+ result <- baker.resolveInteraction(recipeInstanceId, interactionName, event).toBakerResultResponseIO
+ } yield result
+ })
+
+ def metricsClassifier(apiUrlPrefix: String): Request[IO] => Option[String] = { request =>
+ val uriPath = request.uri.path
+ val p = uriPath.takeRight(uriPath.length - apiUrlPrefix.length)
+
+ if (p.startsWith("/app")) Some(p) // cardinality is low, we don't care
+ else if (p.startsWith("/instances")) {
+ val action = p.split('/') // /instances///... - we don't want ID here
+ if (action.length >= 4) Some(s"/instances/${action(3)}") else Some("/instances/state")
+ } else None
+ }
+
+
+ private implicit class BakerResultFutureHelper[A](f: => Future[A]) {
+ def toBakerResultResponseIO(implicit encoder: Encoder[A]): IO[Response[IO]] =
+ IO.fromFuture(IO(f)).toBakerResultResponseIO
+ }
+
+ private implicit class BakerResultIOHelper[A](io: => IO[A]) {
+ def toBakerResultResponseIO(implicit encoder: Encoder[A]): IO[Response[IO]] =
+ io.attempt.flatMap {
+ case Left(e: BakerException) => Ok(BakerResult(e))
+ case Left(e) =>
+ logger.error(s"Unexpected exception happened when calling Baker", e)
+ InternalServerError(s"No other exception but BakerExceptions should be thrown here: ${e.getCause}")
+ case Right(()) => Ok(BakerResult.Ack)
+ case Right(a) => Ok(BakerResult(a))
+ }
+ }
+
+}
diff --git a/http/baker-http-server/src/main/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerConfiguration.scala b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerConfiguration.scala
new file mode 100644
index 000000000..99e425dad
--- /dev/null
+++ b/http/baker-http-server/src/main/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerConfiguration.scala
@@ -0,0 +1,21 @@
+package com.ing.baker.http.server.scaladsl
+
+import com.typesafe.config.Config
+
+case class Http4sBakerServerConfiguration(apiHost: String,
+ apiPort: Int,
+ apiUrlPrefix: String,
+ loggingEnabled: Boolean)
+
+object Http4sBakerServerConfiguration {
+ def fromConfig(config: Config) : Http4sBakerServerConfiguration = {
+ val bakerConfig = config.getConfig("baker")
+
+ Http4sBakerServerConfiguration(
+ apiHost = bakerConfig.getString("api-host"),
+ apiPort = bakerConfig.getInt("api-port"),
+ apiUrlPrefix = bakerConfig.getString("api-url-prefix"),
+ loggingEnabled = bakerConfig.getBoolean("api-logging-enabled")
+ )
+ }
+}
diff --git a/bakery/state/src/test/resources/recipes/ItemReservation.recipe b/http/baker-http-server/src/test/resources/recipes/ItemReservation.recipe
similarity index 100%
rename from bakery/state/src/test/resources/recipes/ItemReservation.recipe
rename to http/baker-http-server/src/test/resources/recipes/ItemReservation.recipe
diff --git a/bakery/state/src/test/resources/recipes/ItemReservationBlocking.recipe b/http/baker-http-server/src/test/resources/recipes/ItemReservationBlocking.recipe
similarity index 100%
rename from bakery/state/src/test/resources/recipes/ItemReservationBlocking.recipe
rename to http/baker-http-server/src/test/resources/recipes/ItemReservationBlocking.recipe
diff --git a/bakery/state/src/test/scala/com/ing/bakery/baker/RecipeLoaderSpec.scala b/http/baker-http-server/src/test/scala/com/ing/baker/http/server/common/RecipeLoaderSpec.scala
similarity index 98%
rename from bakery/state/src/test/scala/com/ing/bakery/baker/RecipeLoaderSpec.scala
rename to http/baker-http-server/src/test/scala/com/ing/baker/http/server/common/RecipeLoaderSpec.scala
index a72e211de..f3a697582 100644
--- a/bakery/state/src/test/scala/com/ing/bakery/baker/RecipeLoaderSpec.scala
+++ b/http/baker-http-server/src/test/scala/com/ing/baker/http/server/common/RecipeLoaderSpec.scala
@@ -1,8 +1,5 @@
-package com.ing.bakery.baker
+package com.ing.baker.http.server.common
-import java.io.{File, FileInputStream}
-import java.nio.file.{Files, Paths}
-import java.util.Base64
import com.ing.baker.compiler.RecipeCompiler
import com.ing.baker.il.CompiledRecipe
import com.ing.baker.recipe.annotations.{FiresEvent, RecipeInstanceId, RequiresIngredient}
@@ -15,6 +12,9 @@ import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
+import java.io.File
+import java.nio.file.{Files, Paths}
+import java.util.Base64
import scala.concurrent.duration._
class RecipeLoaderSpec extends AnyFunSuite with Matchers with BeforeAndAfterAll {
diff --git a/http/baker-http-server/src/test/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerConfigurationSpec.scala b/http/baker-http-server/src/test/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerConfigurationSpec.scala
new file mode 100644
index 000000000..4f7f31204
--- /dev/null
+++ b/http/baker-http-server/src/test/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerConfigurationSpec.scala
@@ -0,0 +1,17 @@
+package com.ing.baker.http.server.scaladsl
+
+import com.typesafe.config.ConfigFactory
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should
+
+class Http4sBakerServerConfigurationSpec extends AnyFlatSpec with should.Matchers {
+
+ "Http4sBakerServiceConfiguration" should "load by default" in {
+ Http4sBakerServerConfiguration.fromConfig(ConfigFactory.load()) shouldBe Http4sBakerServerConfiguration(
+ "0.0.0.0",
+ 8080,
+ "/api/bakery",
+ loggingEnabled = false
+ )
+ }
+}
diff --git a/http/baker-http-server/src/test/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerSpec.scala b/http/baker-http-server/src/test/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerSpec.scala
new file mode 100644
index 000000000..8a9420ba4
--- /dev/null
+++ b/http/baker-http-server/src/test/scala/com/ing/baker/http/server/scaladsl/Http4sBakerServerSpec.scala
@@ -0,0 +1,99 @@
+package com.ing.baker.http.server.scaladsl
+
+import cats.effect.testing.scalatest.AsyncIOSpec
+import cats.effect.{Blocker, IO, Resource}
+import com.ing.baker.http.DashboardConfiguration
+import com.ing.baker.runtime.scaladsl.Baker
+import io.prometheus.client.CollectorRegistry
+import org.http4s.headers.{`Content-Length`, `Content-Type`}
+import org.http4s.implicits._
+import org.http4s.metrics.MetricsOps
+import org.http4s.metrics.prometheus.Prometheus
+import org.http4s._
+import org.mockito.Mockito.{times, verify, when}
+import org.mockito.MockitoSugar.mock
+import org.scalatest.flatspec.AsyncFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+import scala.concurrent.Future
+
+class Http4sBakerServerSpec extends AsyncFlatSpec with AsyncIOSpec with Matchers{
+
+ val bakerMock: Baker = mock[Baker]
+
+ val dashboardConfigurationEnabled: DashboardConfiguration =
+ DashboardConfiguration(enabled = true, applicationName = "TestCluster", clusterInformation = Map.empty)
+ val dashboardConfigurationDisabled: DashboardConfiguration =
+ dashboardConfigurationEnabled.copy(enabled = false)
+
+ def check[A](actual: IO[Response[IO]],
+ expectedStatus: Status,
+ expectedBody: Option[A])(
+ implicit ev: EntityDecoder[IO, A]
+ ): Boolean = {
+ val actualResp = actual.unsafeRunSync()
+ val statusCheck = actualResp.status == expectedStatus
+ val bodyCheck = expectedBody.fold[Boolean](
+ // Verify Response's body is empty.
+ actualResp.body.compile.toVector.unsafeRunSync().isEmpty)(
+ expected => actualResp.as[A].unsafeRunSync() == expected
+ )
+ statusCheck && bodyCheck
+ }
+
+ private def routes(metrics: MetricsOps[IO],
+ blocker: Blocker,
+ dashboardConfiguration: DashboardConfiguration): HttpRoutes[IO] = Http4sBakerServer.routes(
+ baker = bakerMock,
+ apiUrlPrefix = "/api/test",
+ metrics = metrics,
+ dashboardConfiguration = dashboardConfiguration,
+ blocker = blocker
+ )
+
+ private def doRequest(request : Request[IO],
+ dashboardConfiguration: DashboardConfiguration = dashboardConfigurationEnabled) : Response[IO] = {
+ val routesResource: Resource[IO, HttpRoutes[IO]] = for {
+ metrics <- Prometheus.metricsOps[IO](CollectorRegistry.defaultRegistry, "test")
+ blocker <- Blocker[IO]
+ } yield routes(metrics, blocker, dashboardConfiguration)
+
+ routesResource.use(_.orNotFound.run(request)).unsafeRunSync()
+ }
+
+ "the routes" should "give 404 for non-existent urls" in {
+ val response = doRequest(Request(method = Method.GET, uri = uri"/non-existent"))
+ response.status shouldEqual Status.NotFound
+ }
+
+ "the routes" should "serve the dashboard index file if dashboard is enabled" in {
+ val response = doRequest(Request(method = Method.GET, uri = uri"/"))
+ response.status shouldEqual Status.Ok
+ response.headers.get(`Content-Type`).toString shouldEqual "Some(Content-Type: text/html)"
+ }
+
+ "the routes" should "serve the other static files if dashboard is enabled" in {
+ val response = doRequest(Request(method = Method.GET, uri = uri"/main.js"))
+ response.status shouldEqual Status.Ok
+ response.headers.get(`Content-Type`).toString shouldEqual "Some(Content-Type: application/javascript)"
+ }
+
+ "the routes" should "give 404 if dashboard is disabled" in {
+ val response = doRequest(Request(method = Method.GET, uri = uri"/"), dashboardConfigurationDisabled)
+ response.status shouldEqual Status.NotFound
+ }
+
+ "the routes" should "give dashboard_config" in {
+ val response = doRequest(Request(method = Method.GET, uri = uri"/dashboard_config"))
+ response.status shouldEqual Status.Ok
+ response.headers.get(`Content-Length`).toString shouldEqual "Some(Content-Length: 104)"
+ }
+
+ "the routes" should "call the underlying baker implementation" in {
+ when(bakerMock.getAllInteractions).thenReturn(Future.successful(List.empty))
+ val response = doRequest(Request(method = Method.GET, uri = uri"/api/test/app/interactions"))
+ verify(bakerMock, times(1)).getAllInteractions
+ response.status shouldEqual Status.Ok
+ }
+
+}
diff --git a/project/BuildInteractionDockerImageSBTPlugin.scala b/project/BuildInteractionDockerImageSBTPlugin.scala
index a6d50af10..a2f372a5f 100644
--- a/project/BuildInteractionDockerImageSBTPlugin.scala
+++ b/project/BuildInteractionDockerImageSBTPlugin.scala
@@ -161,6 +161,7 @@ object BuildInteractionDockerImageSBTPlugin extends sbt.AutoPlugin {
|import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
|import scala.collection.JavaConverters._
+ |import scala.annotation.nowarn
|import scala.concurrent.ExecutionContext.Implicits.global
|
|/**
@@ -168,7 +169,7 @@ object BuildInteractionDockerImageSBTPlugin extends sbt.AutoPlugin {
| */
|object Main extends App with LazyLogging{
|
- |
+ | @nowarn
| def getImplementations(configurationClassString: String) : List[InteractionInstance] = {
| val configClass = Class.forName(configurationClassString)
| logger.info("Class found: " + configClass)
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 976efdcc7..b88b5ec6b 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -27,7 +27,7 @@ object Dependencies {
val scalaJava8Compat100 = "org.scala-lang.modules" %% "scala-java8-compat" % "1.0.0"
val scalaJava8Compat091 = "org.scala-lang.modules" %% "scala-java8-compat" % "0.9.1"
- val scalaTest = "org.scalatest" %% "scalatest" % "3.2.11"
+ val scalaTest = "org.scalatest" %% "scalatest" % "3.2.12"
val mockitoScala = "org.mockito" %% "mockito-scala" % mockitoScalaVersion
val mockitoScalaTest = "org.mockito" %% "mockito-scala-scalatest" % mockitoScalaVersion
val mockServer = "org.mock-server" % "mockserver-netty" % "5.13.2"
@@ -98,6 +98,7 @@ object Dependencies {
val circeGenericExtras = "io.circe" %% "circe-generic-extras" % circeVersion
val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectVersion
+ val catsEffectTesting = "com.codecommit" %% "cats-effect-testing-scalatest" % "0.5.4"
val catsCore = "org.typelevel" %% "cats-core" % catsCoreVersion
val console4Cats = "dev.profunktor" %% "console4cats" % "0.8.0"
val catsRetry = "com.github.cb372" %% "cats-retry" % "2.1.1"
diff --git a/project/Publish.scala b/project/Publish.scala
index 774e4dd40..d4704b6bb 100644
--- a/project/Publish.scala
+++ b/project/Publish.scala
@@ -16,7 +16,7 @@ object Publish {
val SuppressJavaDocsAndSources = Seq(
doc / sources := Seq(),
packageDoc / publishArtifact := false,
- packageSrc / publishArtifact := false
+ packageSrc / publishArtifact := true
)
val StableToAzureFeed = Seq(
@@ -84,4 +84,4 @@ object Publish {
pushChanges
)
)
-}
\ No newline at end of file
+}
diff --git a/version.sbt b/version.sbt
index ef3dcf062..8d0e33a2a 100644
--- a/version.sbt
+++ b/version.sbt
@@ -1 +1 @@
-ThisBuild / version := "3.5.1-SNAPSHOT"
\ No newline at end of file
+ThisBuild / version := "3.6.0-SNAPSHOT"