commit deba3a2ae0bf85f96850ad007df03808d28a3e1f
parent 6d785d248c57049c11e08fedbf63f1f932492488
Author: Antoine A <>
Date: Sat, 6 Dec 2025 12:15:19 +0100
ebisync: fetch setup
Diffstat:
6 files changed, 96 insertions(+), 26 deletions(-)
diff --git a/debian/libeufin-ebisync.libeufin-ebisync-ebics-fetch.service b/debian/libeufin-ebisync.libeufin-ebisync-ebics-fetch.service
@@ -6,6 +6,7 @@ PartOf=libeufin-ebisync.target
[Service]
User=libeufin-ebisync
ExecStart=/usr/bin/libeufin-ebisync fetch -c /etc/libeufin-ebisync/ebisync.conf
+ExecCondition=/usr/bin/libeufin-ebisync fetch -c /etc/libeufin-ebisync/ebisync.conf --check
Restart=on-failure
RestartSec=1s
StandardOutput=journal
diff --git a/libeufin-ebisync/ebisync.conf b/libeufin-ebisync/ebisync.conf
@@ -37,19 +37,19 @@ FREQUENCY = 30m
# At what time of day should ebics-fetch perform a checkpoint
CHECKPOINT_TIME_OF_DAY = 19:00
-# Where should the ebics file be stored? This can only be azure-blob-storage
-DESTINATION = azure-blob-storage
+# Where should the ebics file be stored? This his can either can be azure-blob-storage or none
+DESTINATION = none
-# Azure API account base url
+# Azure API account base url for azure-blob-storage
AZURE_API_URL =
-# Azure API account name
+# Azure API account name for azure-blob-storage
AZURE_ACCOUNT_NAME =
-# Azure API account key
+# Azure API account key for azure-blob-storage
AZURE_ACCOUNT_KEY =
-# Which Azure Blob Storage container to use
+# Which Azure Blob Storage container to use for azure-blob-storage
AZURE_COUNTAINER =
[ebisyncdb-postgres]
diff --git a/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/azure.kt b/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/azure.kt
@@ -177,10 +177,8 @@ class AzureBlogStorage(
client.put("$name?restype=container")
}
- suspend fun listBlobs(container: String) {
- val res = client.get("$container?restype=container&comp=list")
- val body = res.bodyAsText()
- println("$res $body")
+ suspend fun getContainterMetadata(container: String) {
+ client.get("$container?restype=container&comp=metadata")
}
suspend fun putBlob(container: String, name: String, content: ByteArray, contentType: ContentType) {
diff --git a/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/cli/Fetch.kt b/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/cli/Fetch.kt
@@ -39,20 +39,40 @@ import java.time.temporal.*
import java.io.IOException
import kotlin.time.toKotlinDuration
-suspend fun submit(cfg: EbisyncFetchConfig, client: EbicsClient, db: Database, orders: List<EbicsOrder>) {
- val azure = AzureBlogStorage(cfg.accountName, cfg.accountKey, cfg.apiUrl, client.client)
+sealed interface DestinationClient {
+ data class AzureBlobStorage(val client: AzureBlogStorage, val container: String): DestinationClient
+
+ companion object {
+ fun prepare(dest: Destination, client: HttpClient): DestinationClient? {
+ return when (dest) {
+ Destination.None -> null
+ is Destination.AzureBlobStorage -> DestinationClient.AzureBlobStorage(
+ AzureBlogStorage(dest.accountName, dest.accountKey, dest.apiUrl, client),
+ dest.container
+ )
+ }
+ }
+ }
+
+ suspend fun uploadFile(name: String, xml: ByteArray) {
+ when (this) {
+ is DestinationClient.AzureBlobStorage -> this.client.putBlob(this.container, name, xml, ContentType.Application.Xml)
+ }
+ }
+}
+
+suspend fun submit(dest: DestinationClient, ebics: EbicsClient, db: Database, orders: List<EbicsOrder>) {
for (order in orders) {
try {
- client.download(order) { payload ->
+ ebics.download(order) { payload ->
val doc = order.doc();
if (doc == OrderDoc.acknowledgement) {
// TODO HAC
} else {
payload.unzipEach { fileName, xml ->
val bytes = xml.use { it.readBytes() }
-
logger.info("upload $fileName")
- azure.putBlob(cfg.container, fileName, xml.use { it.readBytes() }, ContentType.Application.Xml)
+ dest.uploadFile(fileName, bytes)
}
}
}
@@ -78,10 +98,28 @@ class Fetch : EbicsCmd() {
private val transientCheckpoint by option("--checkpoint",
help = "Only supported in --transient mode, run a checkpoint"
).flag()
+ private val check by option(
+ help = "Check whether a destination is configured. Exit with 0 if at destination is configured, otherwise 1"
+ ).flag()
override fun run() = cliCmd(logger) {
ebisyncConfig(config).withDb { db, cfg ->
val (clientKeys, bankKeys) = expectFullKeys(cfg)
+
+ val httpClient = httpClient();
+
+ val dest = DestinationClient.prepare(cfg.fetch.destination, httpClient)
+ if (check) {
+ if (dest == null) {
+ logger.info("No destination configured, not starting the fetcher")
+ throw ProgramResult(1)
+ } else {
+ throw ProgramResult(0)
+ }
+ } else if (dest == null) {
+ throw ProgramResult(0)
+ }
+
val client = EbicsClient(
cfg,
httpClient(),
@@ -149,7 +187,7 @@ class Fetch : EbicsCmd() {
}
supportedOrder.filter { it.isDownload() }
}
- submit(cfg.fetch, client, db, orders)
+ submit(dest, client, db, orders)
true
} catch (e: Exception) {
e.fmtLog(logger)
@@ -170,7 +208,7 @@ class Fetch : EbicsCmd() {
haa.orders
}
// TODO pinned starts
- submit(cfg.fetch, client, db, orders)
+ submit(dest, client, db, orders)
true
} catch (e: Exception) {
e.fmtLog(logger)
@@ -191,7 +229,7 @@ class Fetch : EbicsCmd() {
}
if (notifications != null) {
logger.info("Running at real-time notifications reception")
- submit(cfg.fetch, client, db, notifications)
+ submit(dest, client, db, notifications)
}
}
}
diff --git a/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/cli/Setup.kt b/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/cli/Setup.kt
@@ -58,11 +58,13 @@ class Setup: TalerCmd() {
override fun run() = cliCmd(logger) {
val cfg = ebisyncConfig(config)
- val client = httpClient()
+ val httpClient = httpClient()
val ebicsLogger = EbicsLogger(ebicsLog)
+ logger.info("Check EBICS setup")
+
val (clientKeys, bankKeys) = ebicsSetup(
- client,
+ httpClient,
ebicsLogger,
cfg,
cfg,
@@ -78,7 +80,7 @@ class Setup: TalerCmd() {
cfg.withDb { db, _ ->
EbicsClient(
cfg,
- client,
+ httpClient,
db.ebics,
ebicsLogger,
clientKeys,
@@ -129,7 +131,21 @@ class Setup: TalerCmd() {
}*/
}
}
-
+ logger.info("EBICS ready")
+
+ logger.info("Check fetch destination setup")
+ val dest = DestinationClient.prepare(cfg.fetch.destination, httpClient)
+ when (dest) {
+ null -> logger.info("No destination configured")
+ is DestinationClient.AzureBlobStorage -> {
+ dest.client.getContainterMetadata(dest.container)
+ }
+ }
+ if (dest == null) {
+ logger.warn("No destination configured")
+ }
+ logger.info("Fetch destination ready")
+
println("setup ready")
}
}
\ No newline at end of file
diff --git a/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/config.kt b/libeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/config.kt
@@ -69,10 +69,17 @@ class EbisyncFetchConfig(cfg: TalerConfig) {
val frequencyRaw = sect.string("frequency").require()
val checkpointTime = sect.time("checkpoint_time_of_day").require()
- val apiUrl = sect.baseURL("azure_api_url").require()
- val accountName = sect.string("azure_account_name").require()
- val accountKey = sect.string("azure_account_key").require()
- val container = sect.string("azure_container").require()
+ val destination = sect.mapLambda("destination", "auth destination", mapOf(
+ "none" to { Destination.None },
+ "azure-blob-storage" to {
+ Destination.AzureBlobStorage(
+ apiUrl = sect.baseURL("azure_api_url").require(),
+ accountName = sect.string("azure_account_name").require(),
+ accountKey = sect.string("azure_account_key").require(),
+ container = sect.string("azure_container").require()
+ )
+ }
+ )).require()
}
private fun TalerConfig.dbConfig(): DatabaseConfig {
@@ -96,4 +103,14 @@ fun dbConfig(configPath: Path?): DatabaseConfig =
/** Run [lambda] with access to a database conn pool */
suspend fun EbisyncConfig.withDb(lambda: suspend (Database, EbisyncConfig) -> Unit) {
Database(dbCfg).use { lambda(it, this) }
+}
+
+sealed interface Destination {
+ data object None: Destination
+ data class AzureBlobStorage(
+ val apiUrl: BaseURL,
+ val accountName: String,
+ val accountKey: String,
+ val container: String,
+ ): Destination
}
\ No newline at end of file