libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit deba3a2ae0bf85f96850ad007df03808d28a3e1f
parent 6d785d248c57049c11e08fedbf63f1f932492488
Author: Antoine A <>
Date:   Sat,  6 Dec 2025 12:15:19 +0100

ebisync: fetch setup

Diffstat:
Mdebian/libeufin-ebisync.libeufin-ebisync-ebics-fetch.service | 1+
Mlibeufin-ebisync/ebisync.conf | 12++++++------
Mlibeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/azure.kt | 6++----
Mlibeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/cli/Fetch.kt | 54++++++++++++++++++++++++++++++++++++++++++++++--------
Mlibeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/cli/Setup.kt | 24++++++++++++++++++++----
Mlibeufin-ebisync/src/main/kotlin/tech/libeufin/ebisync/config.kt | 25+++++++++++++++++++++----
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