models.config.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otoroshi_2.12 Show documentation
Show all versions of otoroshi_2.12 Show documentation
Lightweight api management on top of a modern http reverse proxy
The newest version!
package otoroshi.models
import akka.http.scaladsl.util.FastFuture
import io.otoroshi.wasm4s.scaladsl.WasmoSettings
import org.joda.time.DateTime
import otoroshi.auth.AuthModuleConfig
import otoroshi.env.Env
import otoroshi.events._
import otoroshi.next.models.{NgPlugins, NgRoute, NgRouteComposition, StoredNgBackend}
import otoroshi.plugins.geoloc.{IpStackGeolocationHelper, MaxMindGeolocationHelper}
import otoroshi.plugins.useragent.UserAgentHelper
import otoroshi.script.Script
import otoroshi.script.plugins.Plugins
import otoroshi.security.IdGenerator
import otoroshi.ssl.{Cert, ClientCertificateValidator}
import otoroshi.storage.BasicStore
import otoroshi.tcp.TcpService
import otoroshi.utils.RegexPool
import otoroshi.utils.clevercloud.CleverCloudClient
import otoroshi.utils.clevercloud.CleverCloudClient.{CleverSettings, UserTokens}
import otoroshi.utils.http.MtlsConfig
import otoroshi.utils.letsencrypt.LetsEncryptSettings
import otoroshi.utils.mailer.MailerSettings
import otoroshi.utils.syntax.implicits._
import play.api.Logger
import play.api.libs.json._
import play.api.libs.ws.WSProxyServer
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
sealed trait IndexSettingsInterval {
def name: String
def json: JsValue = JsString(name)
}
case class IndexSettingsIntervalDay(name: String = "Day") extends IndexSettingsInterval
case class IndexSettingsIntervalWeek(name: String = "Week") extends IndexSettingsInterval
case class IndexSettingsIntervalMonth(name: String = "Month") extends IndexSettingsInterval
case class IndexSettingsIntervalYear(name: String = "Year") extends IndexSettingsInterval
object IndexSettingsInterval {
val Day = IndexSettingsIntervalDay()
val Week = IndexSettingsIntervalWeek()
val Month = IndexSettingsIntervalMonth()
val Year = IndexSettingsIntervalYear()
def parse(str: String): Option[IndexSettingsInterval] = {
str.trim.toLowerCase() match {
case "week" => IndexSettingsInterval.Week.some
case "month" => IndexSettingsInterval.Month.some
case "year" => IndexSettingsInterval.Year.some
case "day" => IndexSettingsInterval.Day.some
case _ => None
}
}
}
case class IndexSettings(
clientSide: Boolean = true,
numberOfShards: Int = 1,
numberOfReplicas: Int = 1,
interval: IndexSettingsInterval = IndexSettingsInterval.Day
) {
def json: JsValue = Json.obj(
"clientSide" -> clientSide,
"interval" -> interval.json,
"numberOfShards" -> numberOfShards,
"numberOfReplicas" -> numberOfReplicas
)
}
object IndexSettings {
def read(opt: Option[JsValue]): IndexSettings = {
opt match {
case None => IndexSettings()
case Some(jsv) => format.reads(jsv).asOpt.getOrElse(IndexSettings())
}
}
val format = new Format[IndexSettings] {
override def reads(json: JsValue): JsResult[IndexSettings] = Try {
IndexSettings(
clientSide = json.select("clientSide").asOpt[Boolean].getOrElse(true),
numberOfShards = json.select("numberOfShards").asOpt[Int].getOrElse(1),
numberOfReplicas = json.select("numberOfReplicas").asOpt[Int].getOrElse(1),
interval = IndexSettingsInterval
.parse(json.select("interval").asOpt[String].getOrElse("Day"))
.getOrElse(IndexSettingsInterval.Day)
)
} match {
case Failure(e) => JsError(e.getMessage)
case Success(is) => JsSuccess(is)
}
override def writes(o: IndexSettings): JsValue = o.json
}
}
case class ElasticAnalyticsConfig(
uris: Seq[String],
index: Option[String] = None,
`type`: Option[String] = None,
user: Option[String] = None,
password: Option[String] = None,
headers: Map[String, String] = Map.empty[String, String],
indexSettings: IndexSettings = IndexSettings(),
mtlsConfig: MtlsConfig = MtlsConfig.default,
applyTemplate: Boolean = true,
version: Option[String] = None,
maxBulkSize: Int = 100,
sendWorkers: Int = 4
) extends Exporter {
def toJson: JsValue = ElasticAnalyticsConfig.format.writes(this)
}
object ElasticAnalyticsConfig {
def read(json: JsValue): Option[ElasticAnalyticsConfig] = format.reads(json).asOpt
val format = new Format[ElasticAnalyticsConfig] {
override def writes(o: ElasticAnalyticsConfig) =
Json.obj(
"clusterUri" -> o.uris.headOption.map(JsString.apply).getOrElse(JsNull).asValue,
"uris" -> o.uris,
"index" -> o.index.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"type" -> o.`type`.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"user" -> o.user.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"password" -> o.password.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"headers" -> JsObject(o.headers.mapValues(JsString.apply)),
"indexSettings" -> o.indexSettings.json,
"mtlsConfig" -> o.mtlsConfig.json,
"applyTemplate" -> o.applyTemplate,
"version" -> o.version.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"maxBulkSize" -> o.maxBulkSize,
"sendWorkers" -> o.sendWorkers
)
override def reads(json: JsValue) =
Try {
val clusterUriValue: Seq[String] =
(json \ "clusterUri").asOpt[String].map(_.trim).filter(_.nonEmpty).map(s => Seq(s)).getOrElse(Seq.empty)
val urisValue: Seq[String] = json.select("uris").asOpt[Seq[String]].getOrElse(Seq.empty)
val uris: Seq[String] = (clusterUriValue ++ urisValue).flatMap { uri =>
if (uri.contains(",")) {
uri.split(",").map(_.trim)
} else {
Seq(uri)
}
}.distinct
if (uris.isEmpty) {
JsError("no cluster uri found at all")
} else {
JsSuccess(
ElasticAnalyticsConfig(
uris = uris,
index = (json \ "index").asOpt[String].map(_.trim).filter(_.nonEmpty),
`type` = (json \ "type").asOpt[String].map(_.trim).filter(_.nonEmpty),
user = (json \ "user").asOpt[String].map(_.trim).filter(_.nonEmpty),
password = (json \ "password").asOpt[String].map(_.trim).filter(_.nonEmpty),
headers = (json \ "headers").asOpt[Map[String, String]].getOrElse(Map.empty[String, String]),
indexSettings = IndexSettings.read((json \ "indexSettings").asOpt[JsValue]),
mtlsConfig = MtlsConfig.read((json \ "mtlsConfig").asOpt[JsValue]),
applyTemplate = (json \ "applyTemplate").asOpt[Boolean].getOrElse(true),
version = (json \ "version").asOpt[String].filter(_.trim.nonEmpty),
maxBulkSize = json.select("maxBulkSize").asOpt[Int].getOrElse(100),
sendWorkers = json.select("sendWorkers").asOpt[Int].getOrElse(4)
)
)
}
} recover { case e =>
JsError(e.getMessage)
} get
}
}
object QuotasAlmostExceededSettings {
val format = new Format[QuotasAlmostExceededSettings] {
override def reads(json: JsValue): JsResult[QuotasAlmostExceededSettings] = Try {
QuotasAlmostExceededSettings(
enabled = json.select("enabled").asOpt[Boolean].getOrElse(false),
dailyQuotasThreshold = json.select("dailyQuotasThreshold").asOpt[Double].getOrElse(0.8),
monthlyQuotasThreshold = json.select("monthlyQuotasThreshold").asOpt[Double].getOrElse(0.8)
)
} match {
case Success(o) => JsSuccess(o)
case Failure(exception) => JsError(exception.getMessage)
}
override def writes(o: QuotasAlmostExceededSettings): JsValue = o.json
}
}
case class QuotasAlmostExceededSettings(
enabled: Boolean,
dailyQuotasThreshold: Double,
monthlyQuotasThreshold: Double
) {
def json: JsValue = Json.obj(
"enabled" -> enabled,
"dailyQuotasThreshold" -> dailyQuotasThreshold,
"monthlyQuotasThreshold" -> monthlyQuotasThreshold
)
}
case class Webhook(
url: String,
headers: Map[String, String] = Map.empty[String, String],
mtlsConfig: MtlsConfig = MtlsConfig.default
) extends Exporter {
def toJson: JsValue = Webhook.format.writes(this)
}
object Webhook {
implicit val format = new Format[Webhook] {
override def reads(json: JsValue): JsResult[Webhook] =
Try {
Webhook(
url = (json \ "url").as[String],
headers = (json \ "headers").asOpt[Map[String, String]].getOrElse(Map.empty),
mtlsConfig = MtlsConfig.read((json \ "mtlsConfig").asOpt[JsValue])
)
} match {
case Failure(e) => JsError(e.getMessage)
case Success(v) => JsSuccess(v)
}
override def writes(o: Webhook): JsValue =
Json.obj(
"url" -> o.url,
"headers" -> o.headers,
"mtlsConfig" -> o.mtlsConfig.json
)
}
}
case class CleverCloudSettings(
consumerKey: String,
consumerSecret: String,
token: String,
secret: String,
orgaId: String
)
object CleverCloudSettings {
implicit val format = Json.format[CleverCloudSettings]
}
case class Proxies(
alertEmails: Option[WSProxyServer] = None,
eventsWebhooks: Option[WSProxyServer] = None,
clevercloud: Option[WSProxyServer] = None,
services: Option[WSProxyServer] = None,
auth: Option[WSProxyServer] = None,
authority: Option[WSProxyServer] = None,
jwk: Option[WSProxyServer] = None,
elastic: Option[WSProxyServer] = None
) {
def toJson: JsValue = Proxies.format.writes(this)
}
object Proxies {
val format = new Format[Proxies] {
override def writes(o: Proxies) =
Json.obj(
"alertEmails" -> WSProxyServerJson.maybeProxyToJson(o.alertEmails),
"eventsWebhooks" -> WSProxyServerJson.maybeProxyToJson(o.eventsWebhooks),
"clevercloud" -> WSProxyServerJson.maybeProxyToJson(o.clevercloud),
"services" -> WSProxyServerJson.maybeProxyToJson(o.services),
"auth" -> WSProxyServerJson.maybeProxyToJson(o.auth),
"authority" -> WSProxyServerJson.maybeProxyToJson(o.authority),
"jwk" -> WSProxyServerJson.maybeProxyToJson(o.jwk),
"elastic" -> WSProxyServerJson.maybeProxyToJson(o.elastic)
)
override def reads(json: JsValue) =
Try {
JsSuccess(
Proxies(
alertEmails = (json \ "alertEmails").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
eventsWebhooks = (json \ "eventsWebhooks").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
clevercloud = (json \ "clevercloud").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
services = (json \ "services").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
auth = (json \ "auth").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
authority = (json \ "authority").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
jwk = (json \ "jwk").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p)),
elastic = (json \ "elastic").asOpt[JsValue].flatMap(p => WSProxyServerJson.proxyFromJson(p))
)
)
} recover { case e =>
JsError(e.getMessage)
} get
}
}
case class GlobalScripts(
enabled: Boolean = false,
transformersRefs: Seq[String] = Seq.empty,
transformersConfig: JsValue = Json.obj(),
validatorRefs: Seq[String] = Seq.empty,
validatorConfig: JsValue = Json.obj(),
preRouteRefs: Seq[String] = Seq.empty,
preRouteConfig: JsValue = Json.obj(),
sinkRefs: Seq[String] = Seq.empty,
sinkConfig: JsValue = Json.obj(),
jobRefs: Seq[String] = Seq.empty,
jobConfig: JsValue = Json.obj()
) {
def json: JsValue = GlobalScripts.format.writes(this)
}
object GlobalScripts {
val format = new Format[GlobalScripts] {
override def writes(o: GlobalScripts): JsValue =
Json.obj(
"enabled" -> o.enabled,
"transformersRefs" -> JsArray(o.transformersRefs.map(JsString.apply)),
"transformersConfig" -> o.transformersConfig,
"validatorRefs" -> JsArray(o.validatorRefs.map(JsString.apply)),
"validatorConfig" -> o.validatorConfig,
"preRouteRefs" -> JsArray(o.preRouteRefs.map(JsString.apply)),
"preRouteConfig" -> o.preRouteConfig,
"sinkRefs" -> JsArray(o.sinkRefs.map(JsString.apply)),
"sinkConfig" -> o.sinkConfig,
"jobRefs" -> JsArray(o.jobRefs.map(JsString.apply)),
"jobConfig" -> o.jobConfig
)
override def reads(json: JsValue): JsResult[GlobalScripts] =
Try {
JsSuccess(
GlobalScripts(
transformersRefs = (json \ "transformersRefs").asOpt[Seq[String]].getOrElse(Seq.empty),
validatorRefs = (json \ "validatorRefs").asOpt[Seq[String]].getOrElse(Seq.empty),
enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false),
transformersConfig = (json \ "transformersConfig").asOpt[JsValue].getOrElse(Json.obj()),
validatorConfig = (json \ "validatorConfig").asOpt[JsValue].getOrElse(Json.obj()),
preRouteConfig = (json \ "preRouteConfig").asOpt[JsValue].getOrElse(Json.obj()),
preRouteRefs = (json \ "preRouteRefs").asOpt[Seq[String]].getOrElse(Seq.empty),
sinkConfig = (json \ "sinkConfig").asOpt[JsValue].getOrElse(Json.obj()),
sinkRefs = (json \ "sinkRefs").asOpt[Seq[String]].getOrElse(Seq.empty),
jobConfig = (json \ "jobConfig").asOpt[JsValue].getOrElse(Json.obj()),
jobRefs = (json \ "jobRefs").asOpt[Seq[String]].getOrElse(Seq.empty)
)
)
} recover { case e =>
JsError(e.getMessage)
} get
}
}
object GeolocationSettings {
val format = new Format[GeolocationSettings] {
override def writes(o: GeolocationSettings): JsValue = o.json
override def reads(json: JsValue): JsResult[GeolocationSettings] =
Try {
JsSuccess(
(json \ "type").as[String] match {
case "none" => NoneGeolocationSettings
case "maxmind" =>
MaxmindGeolocationSettings(
enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false),
path = (json \ "path").asOpt[String].filter(_.trim.nonEmpty).get
)
case "ipstack" =>
IpStackGeolocationSettings(
enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false),
apikey = (json \ "apikey").asOpt[String].filter(_.trim.nonEmpty).get,
timeout = (json \ "timeout").asOpt[Long].getOrElse(2000L)
)
case _ => NoneGeolocationSettings
}
)
} recover { case e =>
JsError(e.getMessage)
} get
}
}
sealed trait GeolocationSettings {
def enabled: Boolean
def find(ip: String)(implicit env: Env, ec: ExecutionContext): Future[Option[JsValue]]
def json: JsValue
}
case object NoneGeolocationSettings extends GeolocationSettings {
def enabled: Boolean = false
def find(ip: String)(implicit env: Env, ec: ExecutionContext): Future[Option[JsValue]] = FastFuture.successful(None)
def json: JsValue = Json.obj("type" -> "none")
}
case class MaxmindGeolocationSettings(enabled: Boolean, path: String) extends GeolocationSettings {
def json: JsValue = Json.obj("type" -> "maxmind", "path" -> path, "enabled" -> enabled)
def find(ip: String)(implicit env: Env, ec: ExecutionContext): Future[Option[JsValue]] = {
enabled match {
case false => FastFuture.successful(None)
case true => MaxMindGeolocationHelper.find(ip, path)
}
}
}
case class IpStackGeolocationSettings(enabled: Boolean, apikey: String, timeout: Long) extends GeolocationSettings {
def json: JsValue = Json.obj("type" -> "ipstack", "apikey" -> apikey, "timeout" -> timeout, "enabled" -> enabled)
def find(ip: String)(implicit env: Env, ec: ExecutionContext): Future[Option[JsValue]] = {
enabled match {
case false => FastFuture.successful(None)
case true => IpStackGeolocationHelper.find(ip, apikey, timeout)
}
}
}
object UserAgentSettings {
val format = new Format[UserAgentSettings] {
override def writes(o: UserAgentSettings): JsValue = o.json
override def reads(json: JsValue): JsResult[UserAgentSettings] =
Try {
JsSuccess(
UserAgentSettings(
enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false)
)
)
} recover { case e =>
JsError(e.getMessage)
} get
}
}
case class UserAgentSettings(enabled: Boolean) {
def json: JsValue = Json.obj("enabled" -> enabled)
def find(ua: String)(implicit env: Env): Option[JsValue] = {
enabled match {
case false => None
case true => UserAgentHelper.userAgentDetails(ua)
}
}
}
case class AutoCert(
enabled: Boolean = false,
caRef: Option[String] = None,
allowed: Seq[String] = Seq.empty,
notAllowed: Seq[String] = Seq.empty,
replyNicely: Boolean = false
) {
def json: JsValue = AutoCert.format.writes(this)
def matches(domain: String): Boolean = {
!notAllowed.exists(p => otoroshi.utils.RegexPool.apply(p).matches(domain)) &&
allowed.exists(p => otoroshi.utils.RegexPool.apply(p).matches(domain))
}
}
object AutoCert {
val format = new Format[AutoCert] {
override def writes(o: AutoCert): JsValue =
Json.obj(
"enabled" -> o.enabled,
"replyNicely" -> o.replyNicely,
"caRef" -> o.caRef.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"allowed" -> JsArray(o.allowed.map(JsString.apply)),
"notAllowed" -> JsArray(o.notAllowed.map(JsString.apply))
)
override def reads(json: JsValue): JsResult[AutoCert] =
Try {
AutoCert(
enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false),
replyNicely = (json \ "replyNicely").asOpt[Boolean].getOrElse(false),
caRef = (json \ "caRef").asOpt[String],
allowed = (json \ "allowed").asOpt[Seq[String]].getOrElse(Seq("*")),
notAllowed = (json \ "notAllowed").asOpt[Seq[String]].getOrElse(Seq.empty)
)
} match {
case Failure(e) => JsError(e.getMessage)
case Success(ac) => JsSuccess(ac)
}
}
}
case class TlsSettings(
defaultDomain: Option[String] = None,
randomIfNotFound: Boolean = false,
includeJdkCaServer: Boolean = true,
includeJdkCaClient: Boolean = true,
trustedCAsServer: Seq[String] = Seq.empty,
bannedAlpnProtocols: Map[String, Seq[String]] = Map.empty
) {
def json: JsValue = TlsSettings.format.writes(this)
}
object TlsSettings {
val format = new Format[TlsSettings] {
override def writes(o: TlsSettings): JsValue =
Json.obj(
"defaultDomain" -> o.defaultDomain.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"randomIfNotFound" -> o.randomIfNotFound,
"includeJdkCaServer" -> o.includeJdkCaServer,
"includeJdkCaClient" -> o.includeJdkCaClient,
"trustedCAsServer" -> JsArray(o.trustedCAsServer.map(JsString.apply)),
"bannedAlpnProtocols" -> o.bannedAlpnProtocols
)
override def reads(json: JsValue): JsResult[TlsSettings] =
Try {
TlsSettings(
defaultDomain = (json \ "defaultDomain").asOpt[String].map(_.trim).filter(_.nonEmpty),
randomIfNotFound = (json \ "randomIfNotFound").asOpt[Boolean].getOrElse(false),
includeJdkCaServer = (json \ "includeJdkCaServer").asOpt[Boolean].getOrElse(true),
includeJdkCaClient = (json \ "includeJdkCaClient").asOpt[Boolean].getOrElse(true),
trustedCAsServer = (json \ "trustedCAsServer").asOpt[Seq[String]].getOrElse(Seq.empty),
bannedAlpnProtocols = (json \ "bannedAlpnProtocols").asOpt[Map[String, Seq[String]]].getOrElse(Map.empty)
)
} match {
case Failure(e) => JsError(e.getMessage)
case Success(ac) => JsSuccess(ac)
}
}
}
case class DefaultTemplates(
route: Option[JsObject] = Json.obj().some, // Option[NgRoute],
service: Option[JsObject] = Json.obj().some, // Option[NgService],
backend: Option[JsObject] = Json.obj().some, // Option[NgBackend],
target: Option[JsObject] = Json.obj().some, // Option[NgTarget],
descriptor: Option[JsObject] = Json.obj().some, // Option[ServiceDescriptor],
apikey: Option[JsObject] = Json.obj().some, // Option[ApiKey],
group: Option[JsObject] = Json.obj().some, // Option[ServiceGroup],
template: Option[JsObject] = Json.obj().some, // Option[ErrorTemplate],
verifier: Option[JsObject] = Json.obj().some, // Option[GlobalJwtVerifier],
authConfig: Option[JsObject] = Json.obj().some, // Option[AuthModuleConfig],
certificate: Option[JsObject] = Json.obj().some, // Option[Cert],
script: Option[JsObject] = Json.obj().some, // Option[Script],
draft: Option[JsObject] = Json.obj().some, // Option[Draft],
tcpService: Option[JsObject] = Json.obj().some, // Option[TcpService],
dataExporter: Option[JsObject] = Json.obj().some, // Option[DataExporterConfig],
tenant: Option[JsObject] = Json.obj().some, // Option[Tenant],
team: Option[JsObject] = Json.obj().some // Option[Team],
) {
def json: JsValue = DefaultTemplates.format.writes(this)
}
object DefaultTemplates {
val format = new Format[DefaultTemplates] {
override def reads(json: JsValue): JsResult[DefaultTemplates] = {
Try {
DefaultTemplates(
route = json.select("route").asOpt[JsObject],
service = json.select("service").asOpt[JsObject],
backend = json.select("backend").asOpt[JsObject],
target = json.select("target").asOpt[JsObject],
descriptor = json.select("descriptor").asOpt[JsObject],
apikey = json.select("apikey").asOpt[JsObject],
group = json.select("group").asOpt[JsObject],
template = json.select("template").asOpt[JsObject],
verifier = json.select("verifier").asOpt[JsObject],
authConfig = json.select("authConfig").asOpt[JsObject],
certificate = json.select("certificate").asOpt[JsObject],
script = json.select("script").asOpt[JsObject],
draft = json.select("draft").asOpt[JsObject],
tcpService = json.select("tcpService").asOpt[JsObject],
dataExporter = json.select("dataExporter").asOpt[JsObject],
tenant = json.select("tenant").asOpt[JsObject],
team = json.select("team").asOpt[JsObject]
)
} match {
case Failure(e) => JsError(e.getMessage)
case Success(ac) => JsSuccess(ac)
}
}
override def writes(o: DefaultTemplates): JsValue = Json.obj(
"route" -> o.route.getOrElse(JsNull).asValue,
"service" -> o.service.getOrElse(JsNull).asValue,
"backend" -> o.backend.getOrElse(JsNull).asValue,
"target" -> o.target.getOrElse(JsNull).asValue,
"descriptor" -> o.descriptor.getOrElse(JsNull).asValue,
"apikey" -> o.apikey.getOrElse(JsNull).asValue,
"group" -> o.group.getOrElse(JsNull).asValue,
"template" -> o.template.getOrElse(JsNull).asValue,
"verifier" -> o.verifier.getOrElse(JsNull).asValue,
"authConfig" -> o.authConfig.getOrElse(JsNull).asValue,
"certificate" -> o.certificate.getOrElse(JsNull).asValue,
"script" -> o.script.getOrElse(JsNull).asValue,
"draft" -> o.draft.getOrElse(JsNull).asValue,
"tcpService" -> o.tcpService.getOrElse(JsNull).asValue,
"dataExporter" -> o.dataExporter.getOrElse(JsNull).asValue,
"tenant" -> o.tenant.getOrElse(JsNull).asValue,
"team" -> o.team.getOrElse(JsNull).asValue
)
}
}
case class TlsWasmoSettings(settings: WasmoSettings = WasmoSettings(), tlsConfig: MtlsConfig = MtlsConfig()) {
def json: JsValue = TlsWasmoSettings.format.writes(this)
def toWasm4sSettings: WasmoSettings = { // here to avoid breaking the API compatibility
settings.tlsConfig match {
case None if !tlsConfig.mtls => settings
case Some(cfg) if cfg.enabled => settings
case _ => {
settings.copy(
tlsConfig = Some(
io.otoroshi.wasm4s.scaladsl.security.TlsConfig(
certs = tlsConfig.certs,
trustedCerts = tlsConfig.trustedCerts,
enabled = tlsConfig.mtls,
loose = tlsConfig.loose,
trustAll = tlsConfig.trustAll
)
)
)
}
}
}
}
object TlsWasmoSettings {
val format = new Format[TlsWasmoSettings] {
override def writes(o: TlsWasmoSettings): JsValue =
Json.obj(
"settings" -> o.settings.json,
"tlsConfig" -> o.tlsConfig.json
)
override def reads(json: JsValue): JsResult[TlsWasmoSettings] = {
Try {
TlsWasmoSettings(
settings = (json \ "settings").as[WasmoSettings](WasmoSettings.format.reads),
tlsConfig = (json \ "tlsConfig").as[MtlsConfig](MtlsConfig.format.reads)
)
} match {
case Failure(e) => JsError(e.getMessage)
case Success(ac) => JsSuccess(ac)
}
}
}
}
case class GlobalConfig(
letsEncryptSettings: LetsEncryptSettings = LetsEncryptSettings(),
lines: Seq[String] = Seq("prod"),
anonymousReporting: Boolean = false,
initWithNewEngine: Boolean = true,
enableEmbeddedMetrics: Boolean = true,
streamEntityOnly: Boolean = true,
autoLinkToDefaultGroup: Boolean = true,
limitConcurrentRequests: Boolean = false, // TODO : true by default
maxConcurrentRequests: Long = 1000,
maxHttp10ResponseSize: Long = 4 * (1024 * 1024),
useCircuitBreakers: Boolean = true,
apiReadOnly: Boolean = false,
u2fLoginOnly: Boolean = false,
maintenanceMode: Boolean = false,
trustXForwarded: Boolean = true,
ipFiltering: IpFiltering = IpFiltering(),
throttlingQuota: Long = BaseQuotas.MaxValue,
perIpThrottlingQuota: Long = BaseQuotas.MaxValue,
elasticReadsConfig: Option[ElasticAnalyticsConfig] = None,
elasticWritesConfigs: Seq[ElasticAnalyticsConfig] = Seq.empty[ElasticAnalyticsConfig],
analyticsWebhooks: Seq[Webhook] = Seq.empty[Webhook],
logAnalyticsOnServer: Boolean = false,
useAkkaHttpClient: Boolean = false,
// TODO: logBodies: Boolean,
alertsWebhooks: Seq[Webhook] = Seq.empty[Webhook],
alertsEmails: Seq[String] = Seq.empty[String],
endlessIpAddresses: Seq[String] = Seq.empty[String],
kafkaConfig: Option[KafkaConfig] = None,
backOfficeAuthRef: Option[String] = None,
cleverSettings: Option[CleverCloudSettings] = None,
mailerSettings: Option[MailerSettings] = None,
statsdConfig: Option[StatsdConfig] = None,
maxWebhookSize: Int = 100,
middleFingers: Boolean = false,
maxLogsSize: Int = 10000,
otoroshiId: String = IdGenerator.uuid,
snowMonkeyConfig: SnowMonkeyConfig = SnowMonkeyConfig(),
proxies: Proxies = Proxies(),
scripts: GlobalScripts = GlobalScripts(),
geolocationSettings: GeolocationSettings = NoneGeolocationSettings,
userAgentSettings: UserAgentSettings = UserAgentSettings(false),
autoCert: AutoCert = AutoCert(),
tlsSettings: TlsSettings = TlsSettings(),
quotasSettings: QuotasAlmostExceededSettings = QuotasAlmostExceededSettings(false, 0.8, 0.8),
plugins: Plugins = Plugins(),
templates: DefaultTemplates = DefaultTemplates(),
wasmoSettings: Option[TlsWasmoSettings] = None,
tags: Seq[String] = Seq.empty,
metadata: Map[String, String] = Map.empty,
env: JsObject = Json.obj(),
extensions: Map[String, JsValue] = Map.empty
) extends Entity {
def internalId: String = "global"
def json: play.api.libs.json.JsValue = toJson
def theMetadata: Map[String, String] = metadata
def theTags: Seq[String] = tags
def theDescription: String = "The global config for otoroshi"
def theName: String = "otoroshi-global-config"
def save()(implicit ec: ExecutionContext, env: Env) = env.datastores.globalConfigDataStore.set(this)
def delete()(implicit ec: ExecutionContext, env: Env) = env.datastores.globalConfigDataStore.delete(this)
def exists()(implicit ec: ExecutionContext, env: Env) = env.datastores.globalConfigDataStore.exists(this)
def toJson = GlobalConfig.toJson(this)
def withinThrottlingQuota()(implicit ec: ExecutionContext, env: Env): Future[Boolean] =
env.datastores.globalConfigDataStore.withinThrottlingQuota()
def cleverClient(implicit env: Env): Option[CleverCloudClient] =
cleverSettings match {
case None => None
case Some(settings) => {
val cleverSetting = CleverSettings(
apiConsumerKey = settings.consumerKey,
apiConsumerSecret = settings.consumerSecret,
apiAuthToken = UserTokens(
token = settings.token,
secret = settings.secret
)
)
Some(CleverCloudClient(env, this, cleverSetting, settings.orgaId))
}
}
def matchesEndlessIpAddresses(ipAddress: String): Boolean = {
if (endlessIpAddresses.nonEmpty) {
endlessIpAddresses.exists { ip =>
if (ip.contains("/")) {
IpFiltering.cidr(ip).contains(ipAddress)
} else {
RegexPool(ip).matches(ipAddress)
}
}
} else {
false
}
}
lazy val incomingRequestValidators = NgPlugins.readFrom(plugins.config.select("incoming_request_validators"))
}
object GlobalConfig {
lazy val logger = Logger("otoroshi-global-config")
val _fmt: Format[GlobalConfig] = new Format[GlobalConfig] {
def readWasmoSettings(json: JsValue): Option[TlsWasmoSettings] = {
if (json.atPath("wasmoSettings.settings").isEmpty) {
val wasmoSettings: JsResult[WasmoSettings] = WasmoSettings.format.reads(
(json \ "wasmoSettings")
.asOpt[JsValue]
.getOrElse(JsNull)
)
val wasmManagerSettings: JsResult[WasmoSettings] = WasmoSettings.format
.reads(
(json \ "wasmManagerSettings")
.asOpt[JsValue]
.getOrElse(JsNull)
)
wasmoSettings
.map(r => r.some)
.getOrElse(wasmManagerSettings.map(r => r.some).getOrElse(None))
.map(value => TlsWasmoSettings(settings = value))
} else {
TlsWasmoSettings.format.reads(
(json \ "wasmoSettings")
.asOpt[JsValue]
.getOrElse(JsNull)
) match {
case JsSuccess(value, path) => value.some
case JsError(_) => TlsWasmoSettings().some
}
}
}
override def writes(o: GlobalConfig): JsValue = {
val mailerSettings: JsValue = o.mailerSettings match {
case None => JsNull
case Some(config) => config.json
}
val cleverSettings: JsValue = o.cleverSettings match {
case None => JsNull
case Some(config) =>
Json.obj(
"consumerKey" -> config.consumerKey,
"consumerSecret" -> config.consumerSecret,
"token" -> config.token,
"secret" -> config.secret,
"orgaId" -> config.orgaId
)
}
val kafkaConfig: JsValue = o.kafkaConfig match {
case None => JsNull
case Some(config) => config.json
// Json.obj(
// "servers" -> JsArray(config.servers.map(JsString.apply)),
// "keyPass" -> config.keyPass.map(JsString.apply).getOrElse(JsNull).as[JsValue],
// "keystore" -> config.keystore.map(JsString.apply).getOrElse(JsNull).as[JsValue],
// "truststore" -> config.truststore.map(JsString.apply).getOrElse(JsNull).as[JsValue],
// "alertsTopic" -> config.alertsTopic,
// "analyticsTopic" -> config.analyticsTopic,
// "auditTopic" -> config.auditTopic,
// "sendEvents" -> config.sendEvents
// )
}
val statsdConfig: JsValue = o.statsdConfig match {
case None => JsNull
case Some(config) =>
Json.obj(
"host" -> config.host,
"port" -> config.port,
"datadog" -> config.datadog
)
}
Json.obj(
"tags" -> JsArray(o.tags.map(JsString.apply)),
"letsEncryptSettings" -> o.letsEncryptSettings.json,
"lines" -> JsArray(o.lines.map(JsString.apply)),
"anonymousReporting" -> o.anonymousReporting,
"initWithNewEngine" -> o.initWithNewEngine,
"maintenanceMode" -> o.maintenanceMode,
"enableEmbeddedMetrics" -> o.enableEmbeddedMetrics,
"streamEntityOnly" -> o.streamEntityOnly,
"autoLinkToDefaultGroup" -> o.autoLinkToDefaultGroup,
"limitConcurrentRequests" -> o.limitConcurrentRequests,
"maxConcurrentRequests" -> o.maxConcurrentRequests,
"maxHttp10ResponseSize" -> o.maxHttp10ResponseSize,
"useCircuitBreakers" -> o.useCircuitBreakers,
"apiReadOnly" -> o.apiReadOnly,
"u2fLoginOnly" -> o.u2fLoginOnly,
"trustXForwarded" -> o.trustXForwarded,
"ipFiltering" -> o.ipFiltering.toJson,
"throttlingQuota" -> o.throttlingQuota,
"perIpThrottlingQuota" -> o.perIpThrottlingQuota,
"analyticsWebhooks" -> JsArray(o.analyticsWebhooks.map(_.toJson)),
"alertsWebhooks" -> JsArray(o.alertsWebhooks.map(_.toJson)),
"elasticWritesConfigs" -> JsArray(o.elasticWritesConfigs.map(_.toJson)),
"elasticReadsConfig" -> o.elasticReadsConfig.map(_.toJson).getOrElse(JsNull).as[JsValue],
"alertsEmails" -> JsArray(o.alertsEmails.map(JsString.apply)),
"logAnalyticsOnServer" -> o.logAnalyticsOnServer,
"useAkkaHttpClient" -> o.useAkkaHttpClient,
"endlessIpAddresses" -> JsArray(o.endlessIpAddresses.map(JsString.apply)),
"statsdConfig" -> statsdConfig,
"kafkaConfig" -> kafkaConfig,
"backOfficeAuthRef" -> o.backOfficeAuthRef.map(JsString.apply).getOrElse(JsNull).as[JsValue],
"mailerSettings" -> mailerSettings,
"cleverSettings" -> cleverSettings,
"maxWebhookSize" -> o.maxWebhookSize,
"middleFingers" -> o.middleFingers,
"maxLogsSize" -> o.maxLogsSize,
"otoroshiId" -> o.otoroshiId,
"snowMonkeyConfig" -> o.snowMonkeyConfig.asJson,
"scripts" -> o.scripts.json,
"geolocationSettings" -> o.geolocationSettings.json,
"userAgentSettings" -> o.userAgentSettings.json,
"autoCert" -> o.autoCert.json,
"tlsSettings" -> o.tlsSettings.json,
"quotasSettings" -> o.quotasSettings.json,
"plugins" -> o.plugins.json,
"wasmoSettings" -> o.wasmoSettings.map(_.json).getOrElse(JsNull).as[JsValue],
"metadata" -> o.metadata,
"env" -> o.env,
"extensions" -> o.extensions,
"templates" -> o.templates.json
)
}
override def reads(json: JsValue): JsResult[GlobalConfig] =
Try {
GlobalConfig(
lines = (json \ "lines").asOpt[Seq[String]].getOrElse(Seq("prod")),
anonymousReporting = (json \ "anonymousReporting").asOpt[Boolean].getOrElse(false),
initWithNewEngine = (json \ "initWithNewEngine").asOpt[Boolean].getOrElse(false),
enableEmbeddedMetrics = (json \ "enableEmbeddedMetrics").asOpt[Boolean].getOrElse(true),
streamEntityOnly = (json \ "streamEntityOnly").asOpt[Boolean].getOrElse(true),
maintenanceMode = (json \ "maintenanceMode").asOpt[Boolean].getOrElse(false),
autoLinkToDefaultGroup = (json \ "autoLinkToDefaultGroup").asOpt[Boolean].getOrElse(true),
trustXForwarded = (json \ "trustXForwarded").asOpt[Boolean].getOrElse(true),
limitConcurrentRequests = (json \ "limitConcurrentRequests")
.asOpt[Boolean]
.getOrElse(false), // TODO : true by default after prod monitoring
maxConcurrentRequests = (json \ "maxConcurrentRequests").asOpt[Long].getOrElse(1000),
maxHttp10ResponseSize = (json \ "maxHttp10ResponseSize").asOpt[Long].getOrElse(4 * (1024 * 1024)),
useCircuitBreakers = (json \ "useCircuitBreakers").asOpt[Boolean].getOrElse(true),
otoroshiId = (json \ "otoroshiId").asOpt[String].getOrElse(s"otoroshi_${IdGenerator.uuid}"),
apiReadOnly = (json \ "apiReadOnly").asOpt[Boolean].getOrElse(false),
u2fLoginOnly = (json \ "u2fLoginOnly").asOpt[Boolean].getOrElse(false),
logAnalyticsOnServer = (json \ "logAnalyticsOnServer").asOpt[Boolean].getOrElse(false),
useAkkaHttpClient = (json \ "useAkkaHttpClient").asOpt[Boolean].getOrElse(false),
ipFiltering = (json \ "ipFiltering").asOpt[IpFiltering](IpFiltering.format).getOrElse(IpFiltering()),
throttlingQuota = (json \ "throttlingQuota").asOpt[Long].getOrElse(BaseQuotas.MaxValue),
perIpThrottlingQuota = (json \ "perIpThrottlingQuota").asOpt[Long].getOrElse(BaseQuotas.MaxValue),
elasticReadsConfig = (json \ "elasticReadsConfig").asOpt[JsObject].flatMap { config =>
ElasticAnalyticsConfig.format.reads(config).asOpt
/*(
(config \ "clusterUri").asOpt[String],
(config \ "index").asOpt[String],
(config \ "type").asOpt[String],
(config \ "user").asOpt[String],
(config \ "password").asOpt[String]
) match {
case (Some(clusterUri), index, typ, user, password) if clusterUri.nonEmpty =>
Some(ElasticAnalyticsConfig(clusterUri, index, typ, user, password))
case e => None
}*/
},
analyticsWebhooks =
(json \ "analyticsWebhooks").asOpt[Seq[Webhook]](Reads.seq(Webhook.format)).getOrElse(Seq.empty[Webhook]),
alertsWebhooks =
(json \ "alertsWebhooks").asOpt[Seq[Webhook]](Reads.seq(Webhook.format)).getOrElse(Seq.empty[Webhook]),
elasticWritesConfigs = (json \ "elasticWritesConfigs")
.asOpt[Seq[ElasticAnalyticsConfig]](Reads.seq(ElasticAnalyticsConfig.format))
.getOrElse(Seq.empty[ElasticAnalyticsConfig]),
alertsEmails = (json \ "alertsEmails").asOpt[Seq[String]].getOrElse(Seq.empty[String]),
endlessIpAddresses = (json \ "endlessIpAddresses").asOpt[Seq[String]].getOrElse(Seq.empty[String]),
maxWebhookSize = (json \ "maxWebhookSize").asOpt[Int].getOrElse(100),
middleFingers = (json \ "middleFingers").asOpt[Boolean].getOrElse(false),
maxLogsSize = (json \ "maxLogsSize").asOpt[Int].getOrElse(10000),
statsdConfig = (json \ "statsdConfig").asOpt[JsValue].flatMap { config =>
(
(config \ "host").asOpt[String].filter(_.nonEmpty),
(config \ "port").asOpt[Int],
(config \ "datadog").asOpt[Boolean].getOrElse(false)
) match {
case (Some(host), Some(port), datadog) =>
Some(StatsdConfig(datadog, host, port))
case e => None
}
},
kafkaConfig = (json \ "kafkaConfig").asOpt[JsValue].flatMap { config =>
KafkaConfig.format.reads(config).asOpt
// (
// (config \ "servers").asOpt[Seq[String]].filter(_.nonEmpty),
// (config \ "keyPass").asOpt[String],
// (config \ "keystore").asOpt[String],
// (config \ "truststore").asOpt[String],
// (config \ "sendEvents").asOpt[Boolean].getOrElse(true),
// (config \ "alertsTopic").asOpt[String].filter(_.nonEmpty),
// (config \ "analyticsTopic").asOpt[String].filter(_.nonEmpty),
// (config \ "auditTopic").asOpt[String].filter(_.nonEmpty)
// ) match {
// case (Some(servers),
// keyPass,
// keystore,
// truststore,
// sendEvents,
// Some(alertsTopic),
// Some(analyticsTopic),
// Some(auditTopic)) =>
// Some(KafkaConfig(servers, keyPass, keystore, truststore, sendEvents, alertsTopic, analyticsTopic, auditTopic))
// case e => None
// }
},
backOfficeAuthRef = (json \ "backOfficeAuthRef").asOpt[String],
mailerSettings = (json \ "mailerSettings").asOpt[JsValue].flatMap { config =>
MailerSettings.format.reads(config).asOpt
},
cleverSettings = (json \ "cleverSettings").asOpt[JsValue].flatMap { config =>
(
(config \ "consumerKey").asOpt[String].filter(_.nonEmpty),
(config \ "consumerSecret").asOpt[String].filter(_.nonEmpty),
(config \ "token").asOpt[String].filter(_.nonEmpty),
(config \ "secret").asOpt[String].filter(_.nonEmpty),
(config \ "orgaId").asOpt[String].filter(_.nonEmpty)
) match {
case (Some(consumerKey), Some(consumerSecret), Some(token), Some(secret), Some(orgaId)) =>
Some(CleverCloudSettings(consumerKey, consumerSecret, token, secret, orgaId))
case _ => None
}
},
snowMonkeyConfig = (json \ "snowMonkeyConfig").asOpt(SnowMonkeyConfig._fmt).getOrElse(SnowMonkeyConfig()),
scripts = GlobalScripts.format
.reads((json \ "scripts").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(GlobalScripts()),
geolocationSettings = GeolocationSettings.format
.reads((json \ "geolocationSettings").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(NoneGeolocationSettings),
userAgentSettings = UserAgentSettings.format
.reads((json \ "userAgentSettings").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(UserAgentSettings(false)),
letsEncryptSettings = LetsEncryptSettings.format
.reads((json \ "letsEncryptSettings").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(LetsEncryptSettings()),
autoCert = AutoCert.format
.reads((json \ "autoCert").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(AutoCert()),
tlsSettings = TlsSettings.format
.reads((json \ "tlsSettings").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(TlsSettings()),
quotasSettings = QuotasAlmostExceededSettings.format
.reads((json \ "quotasSettings").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(QuotasAlmostExceededSettings(false, 0.8, 0.8)),
plugins = Plugins.format
.reads((json \ "plugins").asOpt[JsValue].getOrElse(JsNull))
.getOrElse(Plugins()),
templates = json
.select("templates")
.asOpt[String]
.flatMap(str => DefaultTemplates.format.reads(Json.parse(str)).asOpt)
.orElse(json.select("templates").asOpt(DefaultTemplates.format))
.getOrElse(DefaultTemplates()),
wasmoSettings = readWasmoSettings(json),
metadata = (json \ "metadata").asOpt[Map[String, String]].getOrElse(Map.empty),
env = (json \ "env").asOpt[JsObject].getOrElse(Json.obj()),
extensions = (json \ "extensions").asOpt[Map[String, JsValue]].getOrElse(Map.empty),
tags = (json \ "tags").asOpt[Seq[String]].getOrElse(Seq.empty[String])
)
} map { case sd =>
JsSuccess(sd)
} recover { case t =>
logger.error("Error while reading GlobalConfig", t)
JsError(t.getMessage)
} get
}
def toJson(value: GlobalConfig): JsValue = _fmt.writes(value)
def fromJsons(value: JsValue): GlobalConfig =
try {
_fmt.reads(value).get
} catch {
case e: Throwable => {
logger.error(s"Try to deserialize ${Json.prettyPrint(value)}")
throw e
}
}
def fromJsonSafe(value: JsValue): JsResult[GlobalConfig] = _fmt.reads(value)
}
trait GlobalConfigDataStore extends BasicStore[GlobalConfig] {
def incrementCallsForIpAddressWithTTL(ip: String, ttl: Int = 10)(implicit ec: ExecutionContext): Future[Long]
def quotaForIpAddress(ip: String)(implicit ec: ExecutionContext): Future[Option[Long]]
def isOtoroshiEmpty()(implicit ec: ExecutionContext): Future[Boolean]
def withinThrottlingQuota()(implicit ec: ExecutionContext, env: Env): Future[Boolean]
def updateQuotas(config: otoroshi.models.GlobalConfig)(implicit ec: ExecutionContext, env: Env): Future[Unit]
def singleton()(implicit ec: ExecutionContext, env: Env): Future[GlobalConfig]
def latest()(implicit ec: ExecutionContext, env: Env): GlobalConfig
def latestSafe: Option[GlobalConfig]
def latestUnsafe: GlobalConfig
def fullImport(exportSource: JsObject)(implicit ec: ExecutionContext, env: Env): Future[Unit]
def fullExport()(implicit ec: ExecutionContext, env: Env): Future[JsValue]
def allEnv()(implicit ec: ExecutionContext, env: Env): Future[Set[String]]
def quotasValidationFor(from: String)(implicit ec: ExecutionContext, env: Env): Future[(Boolean, Long, Option[Long])]
def migrate()(implicit ec: ExecutionContext, env: Env): Future[Unit]
def template: GlobalConfig = GlobalConfig()
}
case class OtoroshiExport(
config: GlobalConfig,
descs: Seq[ServiceDescriptor] = Seq.empty,
apikeys: Seq[ApiKey] = Seq.empty,
groups: Seq[ServiceGroup] = Seq.empty,
tmplts: Seq[ErrorTemplate] = Seq.empty,
calls: Long = 0,
dataIn: Long = 0,
dataOut: Long = 0,
admins: Seq[WebAuthnOtoroshiAdmin] = Seq.empty,
simpleAdmins: Seq[SimpleOtoroshiAdmin] = Seq.empty,
jwtVerifiers: Seq[GlobalJwtVerifier] = Seq.empty,
authConfigs: Seq[AuthModuleConfig] = Seq.empty,
certificates: Seq[Cert] = Seq.empty,
clientValidators: Seq[ClientCertificateValidator] = Seq.empty,
scripts: Seq[Script] = Seq.empty,
tcpServices: Seq[TcpService] = Seq.empty,
dataExporters: Seq[DataExporterConfig] = Seq.empty,
tenants: Seq[Tenant] = Seq.empty,
teams: Seq[Team] = Seq.empty,
routes: Seq[NgRoute] = Seq.empty,
routeCompositions: Seq[NgRouteComposition] = Seq.empty,
backends: Seq[StoredNgBackend] = Seq.empty,
wasmPlugins: Seq[WasmPlugin] = Seq.empty,
extensions: Map[String, Map[String, Seq[JsValue]]],
drafts: Seq[Draft] = Seq.empty
) {
import otoroshi.utils.json.JsonImplicits._
import otoroshi.utils.syntax.implicits._
private def customizeAndMergeArray[A](
entities: Seq[A],
arr: JsArray,
fmt: Format[A],
extractIdFromJs: JsValue => String,
extractIdFromEntity: A => String
): Seq[A] = {
val arrWithId = arr.value.map { item =>
val id = extractIdFromJs(item)
(id, item)
}
val (existing, additional) = arrWithId.partition { case (id, item) =>
entities.exists(v => extractIdFromEntity(v) == id)
}
val ex = existing
.map { case (id, item) =>
val entity = entities.find(v => extractIdFromEntity(v) == id).get
val newEntity = fmt.writes(entity).asObject.deepMerge(item.asObject)
fmt.reads(newEntity)
}
.collect { case JsSuccess(s, _) =>
s
}
val add = additional.map(t => fmt.reads(t._2)).collect { case JsSuccess(s, _) =>
s
}
val eid = existing.map(_._1)
val already = entities.filterNot(e => eid.contains(extractIdFromEntity(e)))
already ++ ex ++ add
}
def customizeWith(customization: JsObject)(implicit env: Env): OtoroshiExport = {
val cconfig = customization.select("config").asOpt[JsObject].getOrElse(Json.obj())
val finalConfig = GlobalConfig.fromJsons(config.toJson.asObject.deepMerge(cconfig))
copy(
config = finalConfig,
descs = customizeAndMergeArray[ServiceDescriptor](
descs,
customization.select("descs").asOpt[JsArray].getOrElse(Json.arr()),
ServiceDescriptor._fmt,
_.select("id").asString,
_.id
),
apikeys = customizeAndMergeArray[ApiKey](
apikeys,
customization.select("apikeys").asOpt[JsArray].getOrElse(Json.arr()),
ApiKey._fmt,
_.select("clientId").asString,
_.clientId
),
groups = customizeAndMergeArray[ServiceGroup](
groups,
customization.select("groups").asOpt[JsArray].getOrElse(Json.arr()),
ServiceGroup._fmt,
_.select("id").asString,
_.id
),
tmplts = customizeAndMergeArray[ErrorTemplate](
tmplts,
customization.select("tmplts").asOpt[JsArray].getOrElse(Json.arr()),
ErrorTemplate.format,
_.select("id").asString,
_.serviceId
),
jwtVerifiers = customizeAndMergeArray[GlobalJwtVerifier](
jwtVerifiers,
customization.select("jwtVerifiers").asOpt[JsArray].getOrElse(Json.arr()),
GlobalJwtVerifier._fmt,
_.select("id").asString,
_.id
),
authConfigs = customizeAndMergeArray[AuthModuleConfig](
authConfigs,
customization.select("authConfigs").asOpt[JsArray].getOrElse(Json.arr()),
AuthModuleConfig._fmt(env),
_.select("id").asString,
_.id
),
certificates = customizeAndMergeArray[Cert](
certificates,
customization.select("certificates").asOpt[JsArray].getOrElse(Json.arr()),
Cert._fmt,
_.select("id").asString,
_.id
),
scripts = customizeAndMergeArray[Script](
scripts,
customization.select("scripts").asOpt[JsArray].getOrElse(Json.arr()),
Script._fmt,
_.select("id").asString,
_.id
),
tcpServices = customizeAndMergeArray[TcpService](
tcpServices,
customization.select("tcpServices").asOpt[JsArray].getOrElse(Json.arr()),
TcpService.fmt,
_.select("id").asString,
_.id
),
dataExporters = customizeAndMergeArray[DataExporterConfig](
dataExporters,
customization.select("dataExporters").asOpt[JsArray].getOrElse(Json.arr()),
DataExporterConfig.format,
_.select("id").asString,
_.id
),
tenants = customizeAndMergeArray[Tenant](
tenants,
customization.select("tenants").asOpt[JsArray].getOrElse(Json.arr()),
Tenant.format,
_.select("id").asString,
_.id.value
),
teams = customizeAndMergeArray[Team](
teams,
customization.select("teams").asOpt[JsArray].getOrElse(Json.arr()),
Team.format,
_.select("id").asString,
_.id.value
),
routes = customizeAndMergeArray[NgRoute](
routes,
customization.select("routes").asOpt[JsArray].getOrElse(Json.arr()),
NgRoute.fmt,
_.select("id").asString,
_.id
),
backends = customizeAndMergeArray[StoredNgBackend](
backends,
customization.select("backends").asOpt[JsArray].getOrElse(Json.arr()),
StoredNgBackend.format,
_.select("id").asString,
_.id
),
wasmPlugins = customizeAndMergeArray[WasmPlugin](
wasmPlugins,
customization.select("wasmPlugins").asOpt[JsArray].getOrElse(Json.arr()),
WasmPlugin.format,
_.select("id").asString,
_.id
),
drafts = customizeAndMergeArray[Draft](
drafts,
customization.select("drafts").asOpt[JsArray].getOrElse(Json.arr()),
Draft.format,
_.select("id").asString,
_.id
),
routeCompositions = customizeAndMergeArray[NgRouteComposition](
routeCompositions,
customization.select("routeCompositions").asOpt[JsArray].getOrElse(Json.arr()),
NgRouteComposition.fmt,
_.select("id").asString,
_.id
),
admins = customizeAndMergeArray[WebAuthnOtoroshiAdmin](
admins,
customization.select("admins").asOpt[JsArray].getOrElse(Json.arr()),
WebAuthnOtoroshiAdmin.fmt,
_.select("username").asString,
_.username
),
simpleAdmins = customizeAndMergeArray[SimpleOtoroshiAdmin](
simpleAdmins,
customization.select("simpleAdmins").asOpt[JsArray].getOrElse(Json.arr()),
SimpleOtoroshiAdmin.fmt,
_.select("username").asString,
_.username
)
)
}
def json: JsObject = {
Json.obj(
"label" -> "Otoroshi export",
"dateRaw" -> DateTime.now(),
"date" -> DateTime.now().toString("yyyy-MM-dd hh:mm:ss"),
"stats" -> Json.obj(
"calls" -> calls,
"dataIn" -> dataIn,
"dataOut" -> dataOut
),
"config" -> config.toJson,
"admins" -> JsArray(admins.map(_.json)),
"simpleAdmins" -> JsArray(simpleAdmins.map(_.json)),
"serviceGroups" -> JsArray(groups.map(_.toJson)),
"apiKeys" -> JsArray(apikeys.map(_.toJson)),
"serviceDescriptors" -> JsArray(descs.map(_.toJson)),
"errorTemplates" -> JsArray(tmplts.map(_.toJson)),
"jwtVerifiers" -> JsArray(jwtVerifiers.map(_.asJson)),
"authConfigs" -> JsArray(authConfigs.map(_.asJson)),
"certificates" -> JsArray(certificates.map(_.toJson)),
"clientValidators" -> JsArray(clientValidators.map(_.asJson)),
"scripts" -> JsArray(scripts.map(_.toJson)),
"tcpServices" -> JsArray(tcpServices.map(_.json)),
"dataExporters" -> JsArray(dataExporters.map(_.json)),
"tenants" -> JsArray(tenants.map(_.json)),
"teams" -> JsArray(teams.map(_.json)),
"routes" -> JsArray(routes.map(_.json)),
"routeCompositions" -> JsArray(routeCompositions.map(_.json)),
"backends" -> JsArray(backends.map(_.json)),
"wasmPlugins" -> JsArray(wasmPlugins.map(_.json)),
"drafts" -> JsArray(drafts.map(_.json)),
"extensions" -> extensions
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy