[Trigger-Service] Changes to use a typeconfig conf for trigger-service when provided. (#12217)

* Changes to add the option of starting trigger service with typeconf/HOCON config

CHANGELOG_BEGIN
CHANGELOG_END

* add tests for authorization config and fail on both config file and cli args

* refactor and cleanup config loading and tests

* Changes based on code review comments

* Daml doc changes and making sure that we have defaults for most fields to mirror cli args

CHANGELOG_BEGIN
Trigger Service can now be configured with HOCON config file.
 - If a config file is provided we will choose to start the service using that, else we will fallback to cli arguments.
 - If both config file and cli args are provided we will error out.
CHANGELOG_END

* addressing some more code review comments

* use scalatest inside properly
This commit is contained in:
akshayshirahatti-da 2022-01-06 00:12:47 +00:00 committed by GitHub
parent 39dc4676ad
commit 4f4d18829b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 923 additions and 266 deletions

View File

@ -23,14 +23,110 @@ Starting the Trigger Service
In this example, it is assumed there is a sandbox ledger running on port 6865 on localhost.
.. code-block:: bash
daml trigger-service --config trigger-service.conf
where the corresponding config would be
The required config would look like
.. code-block:: none
{
//dar file containing the trigger
dar-paths = [
"./my-app.dar"
]
//IP address that Trigger service listens on. Defaults to 127.0.0.1.
address = "127.0.0.1"
//Trigger service port number. Defaults to 8088. A port number of 0 will let the system pick an ephemeral port. Consider specifying `port-file` with port number 0.
port = 8088
//port-file = "dummy-port-file"
ledger-api {
address = "localhost"
port = 6865
}
//Optional max inbound message size in bytes. Defaults to 4194304.
max-inbound-message-size = 4194304
//Minimum time interval before restarting a failed trigger. Defaults to 5 seconds.
min-restart-interval = 5s
//Maximum time interval between restarting a failed trigger. Defaults to 60 seconds.
max-restart-interval = 60s
//Optional max HTTP entity upload size in bytes. Defaults to 4194304.
max-http-entity-upload-size = 4194304
//Optional HTTP entity upload timeout. Defaults to 60 seconds.
http-entity-upload-timeout = 60s
//Use static time or wall-clock, default is wall-clock time.
time-provider-type = "wall-clock"
//Compiler config type to use , default or dev mode
compiler-config = "default"
//TTL in seconds used for commands emitted by the trigger. Defaults to 30s.
ttl = 60s
//Initialize database and terminate.
init-db = "false"
//Do not abort if there are existing tables in the database schema. EXPERT ONLY. Defaults to false.
allow-existing-schema = "false"
trigger-store {
user = "postgres"
password = "password"
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/test?&ssl=true"
// prefix for table names to avoid collisions, empty by default
table-prefix = "foo"
// max pool size for the database connection pool
pool-size = 12
//specifies the min idle connections for database connection pool.
min-idle = 4
//specifies the idle timeout for the database connection pool.
idle-timeout = 12s
//specifies the connection timeout for database connection pool.
connection-timeout = 90s
}
authorization {
//Sets both the internal and external auth URIs.
//auth-common-uri = "https://oauth2/common-uri"
// Auth Client to redirect to login , defaults to no
auth-redirect = "yes"
//Sets the internal auth URIs (used by the trigger service to connect directly to the middleware). Overrides value set by auth-common
auth-internal-uri = "https://oauth2/internal-uri"
//Sets the external auth URI (the one returned to the browser). overrides value set by auth-common.
auth-external-uri = "https://oauth2/external-uri"
//URI to the auth login flow callback endpoint `/cb`. By default constructed from the incoming login request.
auth-callback-uri = "https://oauth2/callback-uri"
//Optional max number of pending authorization requests. Defaults to 250.
max-pending-authorizations = 250
//Optional authorization timeout, defaults to 60 seconds
authorization-timeout = 60s
}
}
The above starts the Trigger Service using a number of default parameters. Most notably, the HTTP port the Trigger Service listens on which defaults to 8088.
The above config file should list all available parameters, their defaults and descriptions.
The trigger-service can also be started using cli-args as shown below, one can execute the command ``daml trigger-service --help`` to find all available parameters.
.. note:: Configuration file is the recommended way to run trigger-service, running via cli-args is now deprecated
.. code-block:: bash
daml trigger-service --ledger-host localhost \
--ledger-port 6865 \
--wall-clock-time
The above starts the Trigger Service using a number of default parameters. Most notably, the HTTP port the Trigger Service listens on which defaults to 8088. To see all of the available parameters, their defaults and descriptions, one can execute the command ``daml trigger-service --help``.
Although as we'll see, the Trigger Service exposes an endpoint for end-users to upload DAR files to the service it is sometimes convenient to start the service pre-configured with a specific DAR. To do this, the ``--dar`` option is provided.
.. code-block:: bash

View File

@ -21,10 +21,6 @@ private[http] object JdbcConfig
extends dbutils.ConfigCompanion[JdbcConfig, DBConfig.JdbcConfigDefaults]("JdbcConfig")
with StrictLogging {
final val MinIdle = 8
final val IdleTimeout = 10000L // ms, minimum according to log, defaults to 600s
final val ConnectionTimeout = 5000L
implicit val showInstance: Show[JdbcConfig] = Show.shows { a =>
import a._, baseConfig._
s"JdbcConfig(driver=$driver, url=$url, user=$user, start-mode=$dbStartupMode)"

View File

@ -8,11 +8,13 @@ import org.scalatest.matchers.should.Matchers
import com.daml.dbutils
import com.daml.http.dbbackend.{DbStartupMode, JdbcConfig}
import scala.concurrent.duration._
object CliSpec {
private val poolSize = 10
private val minIdle = 4
private val connectionTimeout = 5000L
private val idleTimeout = 1000L
private val connectionTimeout = 5000.millis
private val idleTimeout = 10000.millis
private val tablePrefix = "foo"
}
final class CliSpec extends AnyFreeSpec with Matchers {
@ -40,7 +42,8 @@ final class CliSpec extends AnyFreeSpec with Matchers {
)
val jdbcConfigString =
"driver=org.postgresql.Driver,url=jdbc:postgresql://localhost:5432/test?&ssl=true,user=postgres,password=password," +
s"poolSize=$poolSize,minIdle=$minIdle,connectionTimeout=$connectionTimeout,idleTimeout=$idleTimeout,tablePrefix=$tablePrefix"
s"poolSize=$poolSize,minIdle=$minIdle,connectionTimeout=${connectionTimeout.toMillis}," +
s"idleTimeout=${idleTimeout.toMillis},tablePrefix=$tablePrefix"
val sharedOptions =
Seq("--ledger-host", "localhost", "--ledger-port", "6865", "--http-port", "7500")
@ -121,7 +124,8 @@ final class CliSpec extends AnyFreeSpec with Matchers {
"DbStartupMode" - {
val jdbcConfigShared =
"driver=org.postgresql.Driver,url=jdbc:postgresql://localhost:5432/test?&ssl=true,user=postgres,password=password," +
s"poolSize=$poolSize,minIdle=$minIdle,connectionTimeout=$connectionTimeout,idleTimeout=$idleTimeout,tablePrefix=$tablePrefix"
s"poolSize=$poolSize,minIdle=$minIdle,connectionTimeout=${connectionTimeout.toMillis}," +
s"idleTimeout=${idleTimeout.toMillis},tablePrefix=$tablePrefix"
"should get the CreateOnly startup mode from the string" in {
val jdbcConfigString = s"$jdbcConfigShared,start-mode=create-only"

View File

@ -63,9 +63,9 @@ object ConnectionPool {
c.setUsername(user)
c.setPassword(password)
c.setMinimumIdle(jc.minIdle)
c.setConnectionTimeout(jc.connectionTimeout)
c.setConnectionTimeout(jc.connectionTimeout.toMillis)
c.setMaximumPoolSize(poolSize)
c.setIdleTimeout(jc.idleTimeout)
c.setIdleTimeout(jc.idleTimeout.toMillis)
new HikariDataSource(c)
}
}

View File

@ -12,6 +12,7 @@ import scalaz.syntax.traverse._
import scalaz.{Show, StateT, \/}
import java.io.File
import scala.concurrent.duration._
import scala.util.Try
object DBConfig {
@ -28,8 +29,8 @@ final case class JdbcConfig(
password: String,
poolSize: Int,
minIdle: Int = JdbcConfig.MinIdle,
connectionTimeout: Long = JdbcConfig.ConnectionTimeout,
idleTimeout: Long = JdbcConfig.IdleTimeout,
connectionTimeout: FiniteDuration = JdbcConfig.ConnectionTimeout,
idleTimeout: FiniteDuration = JdbcConfig.IdleTimeout,
tablePrefix: String = "",
)
@ -105,8 +106,8 @@ object JdbcConfig
with StrictLogging {
final val MinIdle = 8
final val IdleTimeout = 10000L // ms, minimum according to log, defaults to 600s
final val ConnectionTimeout = 5000L
final val IdleTimeout = 10000.millis // minimum according to log, defaults to 600s
final val ConnectionTimeout = 5000.millis
@scala.deprecated("do I need this?", since = "SC")
implicit val showInstance: Show[JdbcConfig] =
@ -163,8 +164,10 @@ object JdbcConfig
tablePrefix <- optionalStringField("tablePrefix").map(_ getOrElse "")
maxPoolSize <- optionalIntField("poolSize").map(_ getOrElse PoolSize.Production)
minIdle <- optionalIntField("minIdle").map(_ getOrElse MinIdle)
connTimeout <- optionalLongField("connectionTimeout").map(_ getOrElse ConnectionTimeout)
idleTimeout <- optionalLongField("idleTimeout").map(_ getOrElse IdleTimeout)
connTimeout <- optionalLongField("connectionTimeout")
.map(x => x.map(_.millis) getOrElse ConnectionTimeout)
idleTimeout <- optionalLongField("idleTimeout")
.map(x => x.map(_.millis) getOrElse IdleTimeout)
} yield JdbcConfig(
driver = driver,
url = url,

View File

@ -41,6 +41,8 @@ da_scala_library(
"@maven//:org_typelevel_cats_effect",
"@maven//:org_typelevel_cats_free",
"@maven//:org_typelevel_cats_kernel",
"@maven//:com_github_pureconfig_pureconfig_core",
"@maven//:com_github_pureconfig_pureconfig_generic",
],
scala_runtime_deps = [
"@maven//:com_typesafe_akka_akka_slf4j",
@ -201,9 +203,14 @@ da_scala_test_suite(
["src/test-suite/scala/**/*.scala"],
exclude = ["**/*Oracle*"],
),
data = [
":src/test-suite/resources/trigger-service.conf",
":src/test-suite/resources/trigger-service-minimal.conf",
],
scala_deps = [
"@maven//:io_spray_spray_json",
"@maven//:com_typesafe_akka_akka_http_core",
"@maven//:com_typesafe_akka_akka_parsing",
"@maven//:org_scalatest_scalatest_core",
"@maven//:org_scalatest_scalatest_matchers_core",
"@maven//:org_scalatest_scalatest_shouldmatchers",
@ -214,9 +221,11 @@ da_scala_test_suite(
deps = [
":trigger-service",
":trigger-service-tests",
"//bazel_tools/runfiles:scala_runfiles",
"//daml-lf/archive:daml_lf_1.dev_archive_proto_java",
"//daml-lf/archive:daml_lf_archive_reader",
"//daml-lf/data",
"//daml-lf/interpreter",
"//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
@ -234,6 +243,7 @@ da_scala_test_suite(
"//libs-scala/ports",
"//libs-scala/postgresql-testing",
"//libs-scala/resources",
"//triggers/service/auth:middleware-api",
"//triggers/service/auth:oauth2-test-server",
"@maven//:eu_rekawek_toxiproxy_toxiproxy_java_2_1_3",
"@maven//:org_flywaydb_flyway_core",

View File

@ -0,0 +1,371 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.lf.engine.trigger
import akka.http.scaladsl.model.Uri
import com.daml.lf.speedy.Compiler
import com.daml.platform.services.time.TimeProviderType
import java.io.File
import java.nio.file.{Path, Paths}
import java.time.Duration
import com.daml.cliopts
import scala.concurrent.duration.FiniteDuration
import com.daml.auth.middleware.api.{Client => AuthClient}
import com.daml.dbutils.{DBConfig, JdbcConfig}
import com.typesafe.scalalogging.StrictLogging
import pureconfig.ConfigSource
import pureconfig.error.ConfigReaderFailures
import scala.concurrent.duration
import scalaz.syntax.std.option._
private[trigger] final case class Cli(
configFile: Option[File],
// For convenience, we allow passing DARs on startup
// as opposed to uploading them dynamically.
darPaths: List[Path],
address: String,
httpPort: Int,
ledgerHost: String,
ledgerPort: Int,
authInternalUri: Option[Uri],
authExternalUri: Option[Uri],
authBothUri: Option[Uri],
authRedirectToLogin: AuthClient.RedirectToLogin,
authCallbackUri: Option[Uri],
maxInboundMessageSize: Int,
minRestartInterval: FiniteDuration,
maxRestartInterval: FiniteDuration,
maxAuthCallbacks: Int,
authCallbackTimeout: FiniteDuration,
maxHttpEntityUploadSize: Long,
httpEntityUploadTimeout: FiniteDuration,
timeProviderType: TimeProviderType,
commandTtl: Duration,
init: Boolean,
jdbcConfig: Option[JdbcConfig],
portFile: Option[Path],
allowExistingSchema: Boolean,
compilerConfig: Compiler.Config,
) extends StrictLogging {
def loadFromConfigFile: Option[Either[ConfigReaderFailures, TriggerServiceAppConf]] =
configFile.map(cf => ConfigSource.file(cf).load[TriggerServiceAppConf])
def loadFromCliArgs: ServiceConfig = {
ServiceConfig(
darPaths = darPaths,
address = address,
httpPort = httpPort,
ledgerHost = ledgerHost,
ledgerPort = ledgerPort,
authInternalUri = authInternalUri,
authExternalUri = authExternalUri,
authBothUri = authBothUri,
authRedirectToLogin = authRedirectToLogin,
authCallbackUri = authCallbackUri,
maxInboundMessageSize = maxInboundMessageSize,
minRestartInterval = minRestartInterval,
maxRestartInterval = maxRestartInterval,
maxAuthCallbacks = maxAuthCallbacks,
authCallbackTimeout = authCallbackTimeout,
maxHttpEntityUploadSize = maxHttpEntityUploadSize,
httpEntityUploadTimeout = httpEntityUploadTimeout,
timeProviderType = timeProviderType,
commandTtl = commandTtl,
init = init,
jdbcConfig = jdbcConfig,
portFile = portFile,
allowExistingSchema = allowExistingSchema,
compilerConfig = compilerConfig,
)
}
def loadConfig: Option[ServiceConfig] =
loadFromConfigFile.cata(
{
case Right(cfg) => Some(cfg.toServiceConfig)
case Left(ex) =>
logger.error(
s"Error loading trigger service config from file ${configFile}",
ex.prettyPrint(),
)
None
},
Some(loadFromCliArgs),
)
}
private[trigger] object Cli {
val DefaultHttpPort: Int = 8088
val DefaultMaxInboundMessageSize: Int = RunnerConfig.DefaultMaxInboundMessageSize
val DefaultMinRestartInterval: FiniteDuration = FiniteDuration(5, duration.SECONDS)
val DefaultMaxRestartInterval: FiniteDuration = FiniteDuration(60, duration.SECONDS)
// Adds up to ~1GB with DefaultMaxInboundMessagesSize
val DefaultMaxAuthCallbacks: Int = 250
val DefaultAuthCallbackTimeout: FiniteDuration = FiniteDuration(1, duration.MINUTES)
val DefaultMaxHttpEntityUploadSize: Long = RunnerConfig.DefaultMaxInboundMessageSize.toLong
val DefaultHttpEntityUploadTimeout: FiniteDuration = FiniteDuration(1, duration.MINUTES)
val DefaultCompilerConfig: Compiler.Config = Compiler.Config.Default
val DefaultCommandTtl: FiniteDuration = FiniteDuration(30, duration.SECONDS)
private[trigger] def redirectToLogin(value: String): AuthClient.RedirectToLogin = {
value.toLowerCase match {
case "yes" => AuthClient.RedirectToLogin.Yes
case "no" => AuthClient.RedirectToLogin.No
case "auto" => AuthClient.RedirectToLogin.Auto
case s =>
throw new IllegalArgumentException(s"value '$s' is not one of 'yes', 'no', or 'auto'.")
}
}
implicit val redirectToLoginRead: scopt.Read[AuthClient.RedirectToLogin] =
scopt.Read.reads(redirectToLogin)
private[trigger] val Default = Cli(
configFile = None,
darPaths = Nil,
address = cliopts.Http.defaultAddress,
httpPort = DefaultHttpPort,
ledgerHost = null,
ledgerPort = 0,
authInternalUri = None,
authExternalUri = None,
authBothUri = None,
authRedirectToLogin = AuthClient.RedirectToLogin.No,
authCallbackUri = None,
maxInboundMessageSize = DefaultMaxInboundMessageSize,
minRestartInterval = DefaultMinRestartInterval,
maxRestartInterval = DefaultMaxRestartInterval,
maxAuthCallbacks = Cli.DefaultMaxAuthCallbacks,
authCallbackTimeout = Cli.DefaultAuthCallbackTimeout,
maxHttpEntityUploadSize = DefaultMaxHttpEntityUploadSize,
httpEntityUploadTimeout = DefaultHttpEntityUploadTimeout,
timeProviderType = TimeProviderType.WallClock,
commandTtl = Duration.ofSeconds(30L),
init = false,
jdbcConfig = None,
portFile = None,
allowExistingSchema = false,
compilerConfig = DefaultCompilerConfig,
)
@SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements")) // scopt builders
private class OptionParser(supportedJdbcDriverNames: Set[String])
extends scopt.OptionParser[Cli]("trigger-service") {
head("trigger-service")
opt[Option[File]]('c', "config")
.text(
"The application config file, this is the recommended way to run the service, individual cli-args are now deprecated"
)
.valueName("<file>")
.action((file, cli) => cli.copy(configFile = file))
opt[String]("dar")
.optional()
.unbounded()
.action((f, c) => c.copy(darPaths = Paths.get(f) :: c.darPaths))
.text("Path to the dar file containing the trigger.")
cliopts.Http.serverParse(this, serviceName = "Trigger")(
address = (f, c) => c.copy(address = f(c.address)),
httpPort = (f, c) => c.copy(httpPort = f(c.httpPort)),
defaultHttpPort = Some(DefaultHttpPort),
portFile = Some((f, c) => c.copy(portFile = f(c.portFile))),
)
opt[String]("ledger-host")
.optional()
.action((t, c) => c.copy(ledgerHost = t))
.text("Ledger hostname.")
opt[Int]("ledger-port")
.optional()
.action((t, c) => c.copy(ledgerPort = t))
.text("Ledger port.")
opt[String]("auth")
.optional()
.action((t, c) => c.copy(authBothUri = Some(Uri(t))))
.text(
"Sets both the internal and external auth URIs. Incompatible with --auth-internal and --auth-external."
)
opt[String]("auth-internal")
.optional()
.action((t, c) => c.copy(authInternalUri = Some(Uri(t))))
.text(
"Sets the internal auth URIs (used by the trigger service to connect directly to the middleware). Incompatible with --auth."
)
opt[String]("auth-external")
.optional()
.action((t, c) => c.copy(authExternalUri = Some(Uri(t))))
.text(
"Sets the external auth URI (the one returned to the browser). Incompatible with --auth."
)
opt[AuthClient.RedirectToLogin]("auth-redirect")
.optional()
.action((x, c) => c.copy(authRedirectToLogin = x))
.text(
"Redirect to auth middleware login endpoint when unauthorized. One of 'yes', 'no', or 'auto'."
)
opt[String]("auth-callback")
.optional()
.action((t, c) => c.copy(authCallbackUri = Some(Uri(t))))
.text(
"URI to the auth login flow callback endpoint `/cb`. By default constructed from the incoming login request."
)
opt[Int]("max-inbound-message-size")
.action((x, c) => c.copy(maxInboundMessageSize = x))
.optional()
.text(
s"Optional max inbound message size in bytes. Defaults to ${DefaultMaxInboundMessageSize}."
)
opt[Long]("min-restart-interval")
.action((x, c) => c.copy(minRestartInterval = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Minimum time interval before restarting a failed trigger. Defaults to ${DefaultMinRestartInterval.toSeconds} seconds."
)
opt[Long]("max-restart-interval")
.action((x, c) => c.copy(maxRestartInterval = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Maximum time interval between restarting a failed trigger. Defaults to ${DefaultMaxRestartInterval.toSeconds} seconds."
)
opt[Int]("max-pending-authorizations")
.action((x, c) => c.copy(maxAuthCallbacks = x))
.optional()
.text(
s"Optional max number of pending authorization requests. Defaults to ${DefaultMaxAuthCallbacks}."
)
opt[Long]("authorization-timeout")
.action((x, c) => c.copy(authCallbackTimeout = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Optional authorization timeout. Defaults to ${DefaultAuthCallbackTimeout.toSeconds} seconds."
)
opt[Long]("max-http-entity-upload-size")
.action((x, c) => c.copy(maxHttpEntityUploadSize = x))
.optional()
.text(s"Optional max HTTP entity upload size. Defaults to ${DefaultMaxHttpEntityUploadSize}.")
opt[Long]("http-entity-upload-timeout")
.action((x, c) => c.copy(httpEntityUploadTimeout = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Optional HTTP entity upload timeout. Defaults to ${DefaultHttpEntityUploadTimeout.toSeconds} seconds."
)
opt[Unit]('s', "static-time")
.optional()
.action((_, c) => c.copy(timeProviderType = TimeProviderType.Static))
.text("Use static time. When not specified, wall-clock time is used.")
opt[Unit]('w', "wall-clock-time")
.optional()
.text(
"[DEPRECATED] Wall-clock time is the default. This flag has no effect. Use `-s` to enable static time."
)
opt[Long]("ttl")
.action { (t, c) =>
c.copy(commandTtl = Duration.ofSeconds(t))
}
.text("TTL in seconds used for commands emitted by the trigger. Defaults to 30s.")
opt[Unit]("dev-mode-unsafe")
.action((_, c) => c.copy(compilerConfig = Compiler.Config.Dev))
.optional()
.text(
"Turns on development mode. Development mode allows development versions of Daml-LF language."
)
.hidden()
implicit val jcd: DBConfig.JdbcConfigDefaults = DBConfig.JdbcConfigDefaults(
supportedJdbcDrivers = supportedJdbcDriverNames,
defaultDriver = Some("org.postgresql.Driver"),
)
opt[Map[String, String]]("jdbc")
.action { (x, c) =>
c.copy(jdbcConfig =
Some(
JdbcConfig
.create(x)
.fold(e => throw new IllegalArgumentException(e), identity)
)
)
}
.optional()
.text(JdbcConfig.help())
.text(
"JDBC configuration parameters. If omitted the service runs without a database. "
+ JdbcConfig.help()
)
opt[Boolean]("allow-existing-schema")
.action((x, c) => c.copy(allowExistingSchema = x))
.text(
"Do not abort if there are existing tables in the database schema. EXPERT ONLY. Defaults to false."
)
checkConfig { cfg =>
if (
(cfg.authBothUri.nonEmpty && (cfg.authInternalUri.nonEmpty || cfg.authExternalUri.nonEmpty))
|| (cfg.authInternalUri.nonEmpty != cfg.authExternalUri.nonEmpty)
)
failure("You must specify either just --auth or both --auth-internal and --auth-external.")
else
success
}
checkConfig { cfg =>
if (cfg.configFile.isEmpty && (cfg.ledgerHost == null || cfg.ledgerPort == 0))
failure(
"Missing required values i.e --ledger-host and/or --ledger-port values for cli args are missing"
)
else
success
}
checkConfig { cfg =>
if (cfg.configFile.isDefined && (cfg.ledgerHost != null || cfg.ledgerPort != 0))
Left("Found both config file and cli opts for the app, please provide only one of them")
else Right(())
}
cmd("init-db")
.action((_, c) => c.copy(init = true))
.text("Initialize database and terminate.")
help("help").text("Print this usage text")
}
def parse(args: Array[String], supportedJdbcDriverNames: Set[String]): Option[Cli] = {
new OptionParser(supportedJdbcDriverNames).parse(args, Default)
}
def parseConfig(
args: Array[String],
supportedJdbcDriverNames: Set[String],
): Option[ServiceConfig] = {
val cli = parse(args, supportedJdbcDriverNames)
cli.flatMap(_.loadConfig)
}
}

View File

@ -5,16 +5,13 @@ package com.daml.lf.engine.trigger
import com.daml.lf.speedy.Compiler
import java.nio.file.{Path, Paths}
import java.nio.file.Path
import java.time.Duration
import akka.http.scaladsl.model.Uri
import com.daml.cliopts
import com.daml.platform.services.time.TimeProviderType
import com.daml.auth.middleware.api.{Client => AuthClient}
import com.daml.dbutils.{DBConfig, JdbcConfig}
import com.daml.dbutils.JdbcConfig
import scala.concurrent.duration
import scala.concurrent.duration.FiniteDuration
private[trigger] final case class ServiceConfig(
@ -45,237 +42,3 @@ private[trigger] final case class ServiceConfig(
allowExistingSchema: Boolean,
compilerConfig: Compiler.Config,
)
private[trigger] object ServiceConfig {
private val DefaultHttpPort: Int = 8088
val DefaultMaxInboundMessageSize: Int = RunnerConfig.DefaultMaxInboundMessageSize
private val DefaultMinRestartInterval: FiniteDuration = FiniteDuration(5, duration.SECONDS)
val DefaultMaxRestartInterval: FiniteDuration = FiniteDuration(60, duration.SECONDS)
// Adds up to ~1GB with DefaultMaxInboundMessagesSize
val DefaultMaxAuthCallbacks: Int = 250
val DefaultAuthCallbackTimeout: FiniteDuration = FiniteDuration(1, duration.MINUTES)
val DefaultMaxHttpEntityUploadSize: Long = RunnerConfig.DefaultMaxInboundMessageSize.toLong
val DefaultHttpEntityUploadTimeout: FiniteDuration = FiniteDuration(1, duration.MINUTES)
val DefaultCompilerConfig: Compiler.Config = Compiler.Config.Default
implicit val redirectToLoginRead: scopt.Read[AuthClient.RedirectToLogin] = scopt.Read.reads {
_.toLowerCase match {
case "yes" => AuthClient.RedirectToLogin.Yes
case "no" => AuthClient.RedirectToLogin.No
case "auto" => AuthClient.RedirectToLogin.Auto
case s => throw new IllegalArgumentException(s"'$s' is not one of 'yes', 'no', or 'auto'.")
}
}
@SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements")) // scopt builders
private class OptionParser(supportedJdbcDriverNames: Set[String])
extends scopt.OptionParser[ServiceConfig]("trigger-service") {
head("trigger-service")
opt[String]("dar")
.optional()
.unbounded()
.action((f, c) => c.copy(darPaths = Paths.get(f) :: c.darPaths))
.text("Path to the dar file containing the trigger.")
cliopts.Http.serverParse(this, serviceName = "Trigger")(
address = (f, c) => c.copy(address = f(c.address)),
httpPort = (f, c) => c.copy(httpPort = f(c.httpPort)),
defaultHttpPort = Some(DefaultHttpPort),
portFile = Some((f, c) => c.copy(portFile = f(c.portFile))),
)
opt[String]("ledger-host")
.required()
.action((t, c) => c.copy(ledgerHost = t))
.text("Ledger hostname.")
opt[Int]("ledger-port")
.required()
.action((t, c) => c.copy(ledgerPort = t))
.text("Ledger port.")
opt[String]("auth")
.optional()
.action((t, c) => c.copy(authBothUri = Some(Uri(t))))
.text(
"Sets both the internal and external auth URIs. Incompatible with --auth-internal and --auth-external."
)
opt[String]("auth-internal")
.optional()
.action((t, c) => c.copy(authInternalUri = Some(Uri(t))))
.text(
"Sets the internal auth URIs (used by the trigger service to connect directly to the middleware). Incompatible with --auth."
)
opt[String]("auth-external")
.optional()
.action((t, c) => c.copy(authExternalUri = Some(Uri(t))))
.text(
"Sets the external auth URI (the one returned to the browser). Incompatible with --auth."
)
opt[AuthClient.RedirectToLogin]("auth-redirect")
.optional()
.action((x, c) => c.copy(authRedirectToLogin = x))
.text(
"Redirect to auth middleware login endpoint when unauthorized. One of 'yes', 'no', or 'auto'."
)
opt[String]("auth-callback")
.optional()
.action((t, c) => c.copy(authCallbackUri = Some(Uri(t))))
.text(
"URI to the auth login flow callback endpoint `/cb`. By default constructed from the incoming login request."
)
opt[Int]("max-inbound-message-size")
.action((x, c) => c.copy(maxInboundMessageSize = x))
.optional()
.text(
s"Optional max inbound message size in bytes. Defaults to ${DefaultMaxInboundMessageSize}."
)
opt[Long]("min-restart-interval")
.action((x, c) => c.copy(minRestartInterval = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Minimum time interval before restarting a failed trigger. Defaults to ${DefaultMinRestartInterval.toSeconds} seconds."
)
opt[Long]("max-restart-interval")
.action((x, c) => c.copy(maxRestartInterval = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Maximum time interval between restarting a failed trigger. Defaults to ${DefaultMaxRestartInterval.toSeconds} seconds."
)
opt[Int]("max-pending-authorizations")
.action((x, c) => c.copy(maxAuthCallbacks = x))
.optional()
.text(
s"Optional max number of pending authorization requests. Defaults to ${DefaultMaxAuthCallbacks}."
)
opt[Long]("authorization-timeout")
.action((x, c) => c.copy(authCallbackTimeout = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Optional authorization timeout. Defaults to ${DefaultAuthCallbackTimeout.toSeconds} seconds."
)
opt[Long]("max-http-entity-upload-size")
.action((x, c) => c.copy(maxHttpEntityUploadSize = x))
.optional()
.text(s"Optional max HTTP entity upload size. Defaults to ${DefaultMaxHttpEntityUploadSize}.")
opt[Long]("http-entity-upload-timeout")
.action((x, c) => c.copy(httpEntityUploadTimeout = FiniteDuration(x, duration.SECONDS)))
.optional()
.text(
s"Optional HTTP entity upload timeout. Defaults to ${DefaultHttpEntityUploadTimeout.toSeconds} seconds."
)
opt[Unit]('s', "static-time")
.optional()
.action((_, c) => c.copy(timeProviderType = TimeProviderType.Static))
.text("Use static time. When not specified, wall-clock time is used.")
opt[Unit]('w', "wall-clock-time")
.optional()
.text(
"[DEPRECATED] Wall-clock time is the default. This flag has no effect. Use `-s` to enable static time."
)
opt[Long]("ttl")
.action { (t, c) =>
c.copy(commandTtl = Duration.ofSeconds(t))
}
.text("TTL in seconds used for commands emitted by the trigger. Defaults to 30s.")
opt[Unit]("dev-mode-unsafe")
.action((_, c) => c.copy(compilerConfig = Compiler.Config.Dev))
.optional()
.text(
"Turns on development mode. Development mode allows development versions of Daml-LF language."
)
.hidden()
implicit val jcd: DBConfig.JdbcConfigDefaults = DBConfig.JdbcConfigDefaults(
supportedJdbcDrivers = supportedJdbcDriverNames,
defaultDriver = Some("org.postgresql.Driver"),
)
opt[Map[String, String]]("jdbc")
.action { (x, c) =>
c.copy(jdbcConfig =
Some(
JdbcConfig
.create(x)
.fold(e => throw new IllegalArgumentException(e), identity)
)
)
}
.optional()
.text(JdbcConfig.help())
.text(
"JDBC configuration parameters. If omitted the service runs without a database. "
+ JdbcConfig.help()
)
opt[Boolean]("allow-existing-schema")
.action((x, c) => c.copy(allowExistingSchema = x))
.text(
"Do not abort if there are existing tables in the database schema. EXPERT ONLY. Defaults to false."
)
checkConfig { cfg =>
if (
(cfg.authBothUri.nonEmpty && (cfg.authInternalUri.nonEmpty || cfg.authExternalUri.nonEmpty))
|| (cfg.authInternalUri.nonEmpty != cfg.authExternalUri.nonEmpty)
)
failure("You must specify either just --auth or both --auth-internal and --auth-external.")
else
success
}
cmd("init-db")
.action((_, c) => c.copy(init = true))
.text("Initialize database and terminate.")
help("help").text("Print this usage text")
}
def parse(args: Array[String], supportedJdbcDriverNames: Set[String]): Option[ServiceConfig] =
new OptionParser(supportedJdbcDriverNames).parse(
args,
ServiceConfig(
darPaths = Nil,
address = cliopts.Http.defaultAddress,
httpPort = DefaultHttpPort,
ledgerHost = null,
ledgerPort = 0,
authInternalUri = None,
authExternalUri = None,
authBothUri = None,
authRedirectToLogin = AuthClient.RedirectToLogin.No,
authCallbackUri = None,
maxInboundMessageSize = DefaultMaxInboundMessageSize,
minRestartInterval = DefaultMinRestartInterval,
maxRestartInterval = DefaultMaxRestartInterval,
maxAuthCallbacks = DefaultMaxAuthCallbacks,
authCallbackTimeout = DefaultAuthCallbackTimeout,
maxHttpEntityUploadSize = DefaultMaxHttpEntityUploadSize,
httpEntityUploadTimeout = DefaultHttpEntityUploadTimeout,
timeProviderType = TimeProviderType.WallClock,
commandTtl = Duration.ofSeconds(30L),
init = false,
jdbcConfig = None,
portFile = None,
allowExistingSchema = false,
compilerConfig = DefaultCompilerConfig,
),
)
}

View File

@ -87,7 +87,7 @@ object ServiceMain {
}
def main(args: Array[String]): Unit = {
ServiceConfig.parse(
Cli.parseConfig(
args,
DbTriggerDao.supportedJdbcDriverNames(JdbcDrivers.availableJdbcDriverNames),
) match {

View File

@ -0,0 +1,138 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.lf.engine.trigger
import akka.http.scaladsl.model.Uri
import com.daml.dbutils.JdbcConfig
import com.daml.lf.speedy.Compiler
import com.daml.platform.services.time.TimeProviderType
import pureconfig.{ConfigReader, ConvertHelpers}
import com.daml.auth.middleware.api.{Client => AuthClient}
import pureconfig.error.FailureReason
import pureconfig.generic.semiauto.deriveReader
import java.nio.file.Path
import java.time.Duration
import scala.concurrent.duration.FiniteDuration
private[trigger] object LedgerApiConfig {
implicit val ledgerApiCfgReader: ConfigReader[LedgerApiConfig] =
deriveReader[LedgerApiConfig]
}
private[trigger] final case class LedgerApiConfig(address: String, port: Int)
private[trigger] object AuthorizationConfig {
final case object AuthConfigFailure extends FailureReason {
val description =
"You must specify either just auth-common-uri or both auth-internal-uri and auth-external-uri"
}
def isValid(ac: AuthorizationConfig): Boolean = {
(ac.authCommonUri.isDefined && ac.authExternalUri.isEmpty && ac.authInternalUri.isEmpty) ||
(ac.authCommonUri.isEmpty && ac.authExternalUri.nonEmpty && ac.authInternalUri.nonEmpty)
}
implicit val uriCfgReader: ConfigReader[Uri] =
ConfigReader.fromString[Uri](ConvertHelpers.catchReadError(s => Uri(s)))
implicit val redirectToLoginCfgReader: ConfigReader[AuthClient.RedirectToLogin] =
ConfigReader.fromString[AuthClient.RedirectToLogin](
ConvertHelpers.catchReadError(s => Cli.redirectToLogin(s))
)
implicit val authCfgReader: ConfigReader[AuthorizationConfig] =
deriveReader[AuthorizationConfig].emap { ac =>
Either.cond(isValid(ac), ac, AuthConfigFailure)
}
}
private[trigger] final case class AuthorizationConfig(
authInternalUri: Option[Uri] = None,
authExternalUri: Option[Uri] = None,
authCommonUri: Option[Uri] = None,
authRedirect: AuthClient.RedirectToLogin = AuthClient.RedirectToLogin.No,
authCallbackUri: Option[Uri] = None,
maxPendingAuthorizations: Int = Cli.DefaultMaxAuthCallbacks,
authCallbackTimeout: FiniteDuration = Cli.DefaultAuthCallbackTimeout,
)
private[trigger] object TriggerServiceAppConf {
implicit val compilerCfgReader: ConfigReader[Compiler.Config] =
ConfigReader.fromString[Compiler.Config](ConvertHelpers.catchReadError { s =>
s.toLowerCase() match {
case "default" => Compiler.Config.Default
case "dev" => Compiler.Config.Dev
case s =>
throw new IllegalArgumentException(
s"Value '$s' for compiler-config is not one of 'default' or 'dev'"
)
}
})
implicit val timeProviderTypeCfgReader: ConfigReader[TimeProviderType] =
ConfigReader.fromString[TimeProviderType](ConvertHelpers.catchReadError { s =>
s.toLowerCase() match {
case "static" => TimeProviderType.Static
case "wall-clock" => TimeProviderType.WallClock
case s =>
throw new IllegalArgumentException(
s"Value '$s' for time-provider-type is not one of 'static' or 'wall-clock'"
)
}
})
implicit val jdbcCfgReader: ConfigReader[JdbcConfig] = deriveReader[JdbcConfig]
implicit val serviceCfgReader: ConfigReader[TriggerServiceAppConf] =
deriveReader[TriggerServiceAppConf]
}
/* An intermediate config representation allowing us to define our HOCON config in a more modular fashion,
this eventually gets mapped to `ServiceConfig`
*/
private[trigger] final case class TriggerServiceAppConf(
darPaths: List[Path] = Nil,
address: String = "127.0.0.1",
port: Int = Cli.DefaultHttpPort,
portFile: Option[Path] = None,
ledgerApi: LedgerApiConfig,
authorization: AuthorizationConfig = AuthorizationConfig(),
maxInboundMessageSize: Int = Cli.DefaultMaxInboundMessageSize,
minRestartInterval: FiniteDuration = Cli.DefaultMinRestartInterval,
maxRestartInterval: FiniteDuration = Cli.DefaultMaxRestartInterval,
maxHttpEntityUploadSize: Long = Cli.DefaultMaxHttpEntityUploadSize,
httpEntityUploadTimeout: FiniteDuration = Cli.DefaultHttpEntityUploadTimeout,
timeProviderType: TimeProviderType = TimeProviderType.WallClock,
ttl: FiniteDuration = Cli.DefaultCommandTtl,
initDb: Boolean = false,
triggerStore: Option[JdbcConfig] = None,
allowExistingSchema: Boolean = false,
compilerConfig: Compiler.Config = Compiler.Config.Default,
) {
def toServiceConfig: ServiceConfig = {
ServiceConfig(
darPaths = darPaths,
address = address,
httpPort = port,
ledgerHost = ledgerApi.address,
ledgerPort = ledgerApi.port,
authInternalUri = authorization.authInternalUri,
authExternalUri = authorization.authExternalUri,
authBothUri = authorization.authCommonUri,
authRedirectToLogin = authorization.authRedirect,
authCallbackUri = authorization.authCallbackUri,
maxInboundMessageSize = maxInboundMessageSize,
minRestartInterval = minRestartInterval,
maxRestartInterval = maxRestartInterval,
maxAuthCallbacks = authorization.maxPendingAuthorizations,
authCallbackTimeout = authorization.authCallbackTimeout,
maxHttpEntityUploadSize = maxHttpEntityUploadSize,
httpEntityUploadTimeout = httpEntityUploadTimeout,
timeProviderType = timeProviderType,
commandTtl =
Duration.ofSeconds(ttl.toSeconds), // mapping from FiniteDuration to java.time.Duration
init = initDb,
jdbcConfig = triggerStore,
portFile = portFile,
allowExistingSchema = allowExistingSchema,
compilerConfig = compilerConfig,
)
}
}

View File

@ -0,0 +1,6 @@
{
ledger-api {
address = "127.0.0.1"
port = 5041
}
}

View File

@ -0,0 +1,81 @@
{
//dar file containing the trigger
dar-paths = [
"./my-app.dar"
]
//IP address that Trigger service listens on. Defaults to 127.0.0.1.
address = "127.0.0.1"
//Trigger service port number. Defaults to 8088. A port number of 0 will let the system pick an ephemeral port. Consider specifying `port-file` option with port number 0.
port = 8088
port-file = "port-file"
ledger-api {
address = "127.0.0.1"
port = 5041
}
//Optional max inbound message size in bytes. Defaults to 4194304.
max-inbound-message-size = 4194304
//Minimum time interval before restarting a failed trigger. Defaults to 5 seconds.
min-restart-interval = 5s
//Maximum time interval between restarting a failed trigger. Defaults to 60 seconds.
max-restart-interval = 60s
//Optional max HTTP entity upload size in bytes. Defaults to 4194304.
max-http-entity-upload-size = 4194304
//Optional HTTP entity upload timeout. Defaults to 60 seconds.
http-entity-upload-timeout = 60s
//Use static time or wall-clock, default is wall-clock time.
time-provider-type = "static"
//Compiler config type to use , default or dev mode
compiler-config = "dev"
//TTL in seconds used for commands emitted by the trigger. Defaults to 30s.
ttl = 60s
//Initialize database and terminate.
init-db = "true"
//Do not abort if there are existing tables in the database schema. EXPERT ONLY. Defaults to false.
allow-existing-schema = "true"
trigger-store {
user = "postgres"
password = "password"
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/test?&ssl=true"
// prefix for table names to avoid collisions, empty by default
table-prefix = "foo"
// max pool size for the database connection pool
pool-size = 12
//specifies the min idle connections for database connection pool.
min-idle = 4
//specifies the idle timeout for the database connection pool.
idle-timeout = 12s
//specifies the connection timeout for database connection pool.
connection-timeout = 90s
}
authorization {
//Sets both the internal and external auth URIs.
//auth-common-uri = "https://oauth2/common-uri"
// Auth Client to redirect to login , defaults to no
auth-redirect = "yes"
//Sets the internal auth URIs (used by the trigger service to connect directly to the middleware). Overrides value set by auth-common
auth-internal-uri = "https://oauth2/internal-uri"
//Sets the external auth URI (the one returned to the browser). overrides value set by auth-common.
auth-external-uri = "https://oauth2/external-uri"
//URI to the auth login flow callback endpoint `/cb`. By default constructed from the incoming login request.
auth-callback-uri = "https://oauth2/callback-uri"
//Optional max number of pending authorization requests. Defaults to 250.
max-pending-authorizations = 250
//Optional authorization timeout, defaults to 60 seconds
authorization-timeout = 60s
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.lf.engine.trigger
import org.scalatest.Succeeded
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import pureconfig.ConfigSource
import pureconfig.error.{ConfigReaderFailures, ConvertFailure}
import org.scalatest.Assertion
class AuthorizationConfigTest extends AsyncWordSpec with Matchers {
private def validateFailure(ex: ConfigReaderFailures): Assertion = {
ex.head match {
case ConvertFailure(reason, _, _) =>
reason shouldBe a[AuthorizationConfig.AuthConfigFailure.type]
reason.description shouldBe AuthorizationConfig.AuthConfigFailure.description
case _ => fail("Unexpected failure type expected `AuthConfigFailure`")
}
}
"should error on specifying both authCommonUri and authInternalUri/authExternalUri" in {
val invalidConfigs = List(
"""
|{
| auth-common-uri = "https://oauth2/common-uri"
| auth-internal-uri = "https://oauth2/internal-uri"
|}
|""".stripMargin,
"""
|{
| auth-common-uri = "https://oauth2/common-uri"
| auth-external-uri = "https://oauth2/external-uri"
|}
|""".stripMargin,
)
invalidConfigs.foreach { c =>
ConfigSource.string(c).load[AuthorizationConfig] match {
case Right(_) =>
fail("Should fail on supplying both auth-common and auth-internal/auth-external uris")
case Left(ex) =>
validateFailure(ex)
}
}
Succeeded
}
"should error on specifying only authInternalUri and no authExternalUri" in {
ConfigSource
.string("""
|{
| auth-internal-uri = "https://oauth2/internal-uri"
|}
|""".stripMargin)
.load[AuthorizationConfig] match {
case Right(_) => fail("Should fail on only auth-internal uris")
case Left(ex) =>
validateFailure(ex)
}
}
}

View File

@ -8,9 +8,9 @@ import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class ServiceConfigTest extends AnyWordSpec with Matchers with OptionValues {
class CliConfigTest extends AnyWordSpec with Matchers with OptionValues {
"parse" should {
import ServiceConfig.parse
import Cli.parse
import com.daml.cliopts.Http.defaultAddress
val baseOpts = Array("--ledger-host", "localhost", "--ledger-port", "9999")

View File

@ -0,0 +1,124 @@
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.lf.engine.trigger
import akka.http.scaladsl.model.Uri
import com.daml.auth.middleware.api.{Client => AuthClient}
import com.daml.bazeltools.BazelRunfiles.requiredResource
import com.daml.dbutils.JdbcConfig
import com.daml.lf.speedy.Compiler
import com.daml.platform.services.time.TimeProviderType
import org.scalatest.Inside.inside
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import pureconfig.error.{CannotReadFile, ConfigReaderFailures}
import java.nio.file.Paths
import scala.concurrent.duration._
class CliSpec extends AsyncWordSpec with Matchers {
val minimalConf = TriggerServiceAppConf(ledgerApi = LedgerApiConfig("127.0.0.1", 5041))
val confFile = "triggers/service/src/test-suite/resources/trigger-service.conf"
def loadCli(file: String): Cli = {
Cli.parse(Array("--config", file), Set()).getOrElse(fail("Could not load Cli on parse"))
}
"should pickup the config file provided" in {
val file = requiredResource(confFile)
val cli = loadCli(file.getAbsolutePath)
cli.configFile should not be empty
}
"should be able to successfully load the config based on the file provided" in {
val expectedAuthCfg = AuthorizationConfig(
authInternalUri = Some(Uri("https://oauth2/internal-uri")),
authExternalUri = Some(Uri("https://oauth2/external-uri")),
authCallbackUri = Some(Uri("https://oauth2/callback-uri")),
authRedirect = AuthClient.RedirectToLogin.Yes,
)
val expectedJdbcConfig = JdbcConfig(
url = "jdbc:postgresql://localhost:5432/test?&ssl=true",
driver = "org.postgresql.Driver",
user = "postgres",
password = "password",
poolSize = 12,
idleTimeout = 12.seconds,
connectionTimeout = 90.seconds,
tablePrefix = "foo",
minIdle = 4,
)
val file = requiredResource(confFile)
val cli = loadCli(file.getAbsolutePath)
cli.configFile should not be empty
val cfg = cli.loadFromConfigFile
inside(cfg) { case Some(Right(c)) =>
c shouldBe minimalConf.copy(
darPaths = List(Paths.get("./my-app.dar")),
portFile = Some(Paths.get("port-file")),
authorization = expectedAuthCfg,
triggerStore = Some(expectedJdbcConfig),
timeProviderType = TimeProviderType.Static,
compilerConfig = Compiler.Config.Dev,
initDb = true,
ttl = 60.seconds,
allowExistingSchema = true,
)
}
}
"should take default values on loading minimal config" in {
val file =
requiredResource("triggers/service/src/test-suite/resources/trigger-service-minimal.conf")
val cli = loadCli(file.getAbsolutePath)
cli.configFile should not be empty
val cfg = cli.loadFromConfigFile
inside(cfg) { case Some(Right(c)) =>
c shouldBe minimalConf
}
}
"parse should raise error on non-existent config file" in {
val cli = loadCli("missingFile.conf")
cli.configFile should not be empty
val cfg = cli.loadFromConfigFile
inside(cfg) { case Some(Left(ConfigReaderFailures(head))) =>
head shouldBe a[CannotReadFile]
}
//parseConfig for non-existent file should return a None
Cli.parseConfig(
Array(
"--config",
"missingFile.conf",
),
Set(),
) shouldBe None
}
"should load config from cli args when no conf file is specified" in {
Cli
.parseConfig(
Array("--ledger-host", "localhost", "--ledger-port", "9999"),
Set(),
) shouldBe Some(Cli.Default.copy(ledgerHost = "localhost", ledgerPort = 9999))
.map(_.loadFromCliArgs)
}
"should fail to load config from cli args on missing required params" in {
Cli
.parseConfig(
Array("--ledger-host", "localhost"),
Set(),
) shouldBe None
}
"should fail to load config on supplying both cli args and config file" in {
Cli
.parseConfig(
Array("--config", confFile, "--ledger-host", "localhost", "--ledger-port", "9999"),
Set(),
) shouldBe None
}
}

View File

@ -520,20 +520,20 @@ trait TriggerServiceFixture
toxiSandboxPort.value,
TimeProviderType.Static,
java.time.Duration.ofSeconds(30),
ServiceConfig.DefaultMaxInboundMessageSize,
Cli.DefaultMaxInboundMessageSize,
)
val restartConfig = TriggerRestartConfig(
minRestartInterval,
ServiceConfig.DefaultMaxRestartInterval,
Cli.DefaultMaxRestartInterval,
)
for {
r <- ServiceMain.startServer(
host.getHostName,
Port.Dynamic.value,
ServiceConfig.DefaultMaxAuthCallbacks,
ServiceConfig.DefaultAuthCallbackTimeout,
ServiceConfig.DefaultMaxHttpEntityUploadSize,
ServiceConfig.DefaultHttpEntityUploadTimeout,
Cli.DefaultMaxAuthCallbacks,
Cli.DefaultAuthCallbackTimeout,
Cli.DefaultMaxHttpEntityUploadSize,
Cli.DefaultHttpEntityUploadTimeout,
authConfig,
AuthClient.RedirectToLogin.Yes,
authCallback,