aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-09-24 17:00:22 +0200
committerFlorian Dold <florian@dold.me>2023-09-24 17:00:22 +0200
commit58b39bd5f70c3edc3594b0571cd6b5575ed9512e (patch)
tree4234cf7895178bc9a539d55aea079dfee98428aa
parent12015ed6dd0e1e91c9ce400ba332ee0bba9a7e42 (diff)
downloadlibeufin-58b39bd5f70c3edc3594b0571cd6b5575ed9512e.tar.gz
libeufin-58b39bd5f70c3edc3594b0571cd6b5575ed9512e.tar.bz2
libeufin-58b39bd5f70c3edc3594b0571cd6b5575ed9512e.zip
native dbinit
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt6
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Database.kt72
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Main.kt34
-rw-r--r--bank/src/test/kotlin/Common.kt20
-rw-r--r--bank/src/test/kotlin/TalerApiTest.kt6
-rwxr-xr-xcontrib/libeufin-bank-dbinit107
-rw-r--r--contrib/libeufin-bank.conf2
-rw-r--r--util/src/main/kotlin/TalerConfig.kt14
-rw-r--r--util/src/test/kotlin/TalerConfigTest.kt1
9 files changed, 128 insertions, 134 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index bfae12c4..e1939073 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -272,9 +272,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
}
post("/withdrawals/{withdrawal_id}/abort") {
- val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw unauthorized()
- // Admin allowed to abort.
- if (!call.getResourceName("USERNAME").canI(c)) throw forbidden()
val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
// Idempotency:
if (op.aborted) {
@@ -290,9 +287,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
}
post("/withdrawals/{withdrawal_id}/confirm") {
- val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: throw unauthorized()
- // No admin allowed.
- if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) throw forbidden()
val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
// Checking idempotency:
if (op.confirmationDone) {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 8bcb5e56..44d0b61a 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -24,9 +24,10 @@ import org.postgresql.jdbc.PgConnection
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import tech.libeufin.util.getJdbcConnectionFromPg
-import java.net.URI
+import java.io.File
import java.sql.DriverManager
import java.sql.PreparedStatement
+import java.sql.ResultSet
import java.sql.SQLException
import java.util.*
import kotlin.math.abs
@@ -41,6 +42,75 @@ fun BankAccountTransaction.expectRowId(): Long = this.dbRowId ?: throw internalS
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Database")
+fun initializeDatabaseTables(dbConfig: String, sqlDir: String) {
+ logger.info("doing DB initialization, sqldir $sqlDir, dbConfig $dbConfig")
+ val jdbcConnStr = getJdbcConnectionFromPg(dbConfig)
+ logger.info("connecting to database via JDBC string '$jdbcConnStr'")
+ val dbConn = DriverManager.getConnection(jdbcConnStr).unwrap(PgConnection::class.java)
+ if (dbConn == null) {
+ throw Error("could not open database")
+ }
+ val sqlVersioning = File("$sqlDir/versioning.sql").readText()
+ dbConn.execSQLUpdate(sqlVersioning)
+
+ val checkStmt = dbConn.prepareStatement("SELECT count(*) as n FROM _v.patches where patch_name = ?")
+
+ for (n in 1..9999) {
+ val numStr = n.toString().padStart(4, '0')
+ val patchName = "libeufin-bank-$numStr"
+
+ checkStmt.setString(1, patchName)
+ val res = checkStmt.executeQuery()
+ if (!res.next()) {
+ throw Error("unable to query patches")
+ }
+
+ val patchCount = res.getInt("n")
+ if (patchCount >= 1) {
+ logger.info("patch $patchName already applied")
+ continue
+ }
+
+ val path = File("$sqlDir/libeufin-bank-$numStr.sql")
+ if (!path.exists()) {
+ logger.info("path $path doesn't exist anymore, stopping")
+ break
+ }
+ logger.info("applying patch $path")
+ val sqlPatchText = path.readText()
+ dbConn.execSQLUpdate(sqlPatchText)
+ }
+ val sqlProcedures = File("$sqlDir/procedures.sql").readText()
+ dbConn.execSQLUpdate(sqlProcedures)
+}
+
+private fun countRows(rs: ResultSet): Int {
+ var size = 0
+ while (rs.next()) {
+ size++
+ }
+ return size
+}
+
+fun resetDatabaseTables(dbConfig: String, sqlDir: String) {
+ logger.info("doing DB initialization, sqldir $sqlDir, dbConfig $dbConfig")
+ val jdbcConnStr = getJdbcConnectionFromPg(dbConfig)
+ logger.info("connecting to database via JDBC string '$jdbcConnStr'")
+ val dbConn = DriverManager.getConnection(jdbcConnStr).unwrap(PgConnection::class.java)
+ if (dbConn == null) {
+ throw Error("could not open database")
+ }
+
+ val queryRes = dbConn.execSQLQuery("SELECT schema_name FROM information_schema.schemata WHERE schema_name='_v'")
+ if (countRows(queryRes) == 0) {
+ logger.info("versioning schema not present, not running drop sql")
+ return
+ }
+
+ val sqlDrop = File("$sqlDir/libeufin-bank-drop.sql").readText()
+ dbConn.execSQLUpdate(sqlDrop)
+}
+
class Database(private val dbConfig: String, private val bankCurrency: String) {
private var dbConn: PgConnection? = null
private var dbCtr: Int = 0
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index cb4bb7f9..025ff5e8 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -287,6 +287,7 @@ fun Application.corebankWebApp(db: Database, ctx: BankApplicationContext) {
class LibeufinBankCommand : CliktCommand() {
init {
versionOption(getVersion())
+ subcommands(ServeBank(), BankDbInit())
}
override fun run() = Unit
@@ -374,6 +375,35 @@ fun readBankApplicationContextFromConfig(cfg: TalerConfig): BankApplicationConte
)
}
+
+class BankDbInit : CliktCommand("Initialize the libeufin-bank database", name = "dbinit") {
+ private val configFile by option(
+ "--config", "-c",
+ help = "set the configuration file"
+ )
+
+ private val requestReset by option(
+ "--reset", "-r",
+ help = "reset database (DANGEROUS: All existing data is lost)"
+ ).flag()
+
+ init {
+ context {
+ helpFormatter = CliktHelpFormatter(showDefaultValues = true)
+ }
+ }
+
+ override fun run() {
+ val config = TalerConfig.load(this.configFile)
+ val dbConnStr = config.requireValueString("libeufin-bankdb", "config")
+ val sqlDir = config.requireValuePath("libeufin-bankdb-postgres", "sql_dir")
+ if (requestReset) {
+ resetDatabaseTables(dbConnStr, sqlDir)
+ }
+ initializeDatabaseTables(dbConnStr, sqlDir)
+ }
+}
+
class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve") {
private val configFile by option(
"--config", "-c",
@@ -388,7 +418,7 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
override fun run() {
val config = TalerConfig.load(this.configFile)
val ctx = readBankApplicationContextFromConfig(config)
- val dbConnStr = config.requireValueString("libeufin-bank-db-postgres", "config")
+ val dbConnStr = config.requireValueString("libeufin-bankdb", "config")
logger.info("using database '$dbConnStr'")
val serveMethod = config.requireValueString("libeufin-bank", "serve")
if (serveMethod.lowercase() != "tcp") {
@@ -407,5 +437,5 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
}
fun main(args: Array<String>) {
- LibeufinBankCommand().subcommands(ServeBank()).main(args)
+ LibeufinBankCommand().main(args)
}
diff --git a/bank/src/test/kotlin/Common.kt b/bank/src/test/kotlin/Common.kt
index 8206c93d..01a36985 100644
--- a/bank/src/test/kotlin/Common.kt
+++ b/bank/src/test/kotlin/Common.kt
@@ -17,25 +17,19 @@
* <http://www.gnu.org/licenses/>
*/
-import tech.libeufin.bank.BankApplicationContext
-import tech.libeufin.bank.Database
-import tech.libeufin.bank.TalerAmount
+import tech.libeufin.bank.*
import tech.libeufin.util.execCommand
/**
* Init the database and sets the currency to KUDOS.
*/
fun initDb(): Database {
- execCommand(
- listOf(
- "libeufin-bank-dbinit",
- "-d",
- "libeufincheck",
- "-r"
- ),
- throwIfFails = true
- )
- return Database("postgresql:///libeufincheck", "KUDOS")
+ val config = TalerConfig.load()
+ val sqlPath = config.requireValuePath("libeufin-bankdb-postgres", "SQL_DIR")
+ val dbConnStr = "postgresql:///libeufincheck"
+ resetDatabaseTables(dbConnStr, sqlPath)
+ initializeDatabaseTables(dbConnStr, sqlPath)
+ return Database(dbConnStr, "KUDOS")
}
fun getTestContext(
diff --git a/bank/src/test/kotlin/TalerApiTest.kt b/bank/src/test/kotlin/TalerApiTest.kt
index 64a33bac..e76c367d 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -262,7 +262,7 @@ class TalerApiTest {
application {
corebankWebApp(db, ctx)
}
- client.post("/accounts/foo/withdrawals/${uuid}/abort") {
+ client.post("/withdrawals/${uuid}/abort") {
expectSuccess = true
basicAuth("foo", "pw")
}
@@ -292,7 +292,7 @@ class TalerApiTest {
}
val opId = Json.decodeFromString<BankAccountCreateWithdrawalResponse>(r.bodyAsText())
// Getting the withdrawal from the bank. Throws (failing the test) if not found.
- client.get("/accounts/foo/withdrawals/${opId.withdrawal_id}") {
+ client.get("/withdrawals/${opId.withdrawal_id}") {
expectSuccess = true
basicAuth("foo", "pw")
}
@@ -328,7 +328,7 @@ class TalerApiTest {
application {
corebankWebApp(db, ctx)
}
- client.post("/accounts/foo/withdrawals/${uuid}/confirm") {
+ client.post("/withdrawals/${uuid}/confirm") {
expectSuccess = true // Sufficient to assert on success.
basicAuth("foo", "pw")
}
diff --git a/contrib/libeufin-bank-dbinit b/contrib/libeufin-bank-dbinit
deleted file mode 100755
index 8425070c..00000000
--- a/contrib/libeufin-bank-dbinit
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-# The only CLI argument is 'nexus' or 'sandbox',
-# indicating which service will get its database prepared.
-
-fail () {
- echo $1
- exit 1
-}
-
-usage_and_exit () {
- echo Usage: libeufin-bank-dbinit OPTIONS
- echo
- echo By default, this command creates and/or patches the Sandbox tables.
- echo Pass '-r' to drop the tables before creating them again.
- echo
- echo 'Supported options:'
- echo ' -d DB_CONN -- required. Pass DB_CONN as the postgres connection string. Passed verbatim to Psql'
- echo ' -l LOC -- required. Pass LOC as the SQL files location. Typically $prefix/share/libeufin/sql/bank'
- echo ' -h -- print this help'
- echo ' -r -- drop all the tables before creating them again'
- exit 0
-}
-
-run_sql_file () {
- # -q doesn't hide all the output, hence the
- # redirection to /dev/null.
- psql -d $DB_CONNECTION \
- -q \
- -f $1 \
- --set ON_ERROR_STOP=1 > /dev/null
-}
-
-get_patch_path () {
- echo "$PATCHES_LOCATION/$1"
-}
-
-# The real check happens (by the caller)
-# by checking the returned text.
-check_patch_applied () {
- psql -d $DB_CONNECTION \
- -t \
- -c "SELECT applied_by FROM _v.patches WHERE patch_name = '$1' LIMIT 1"
-}
-
-# Iterates over the .sql migration files and applies
-# the new ones.
-iterate_over_patches () {
- cd $PATCHES_LOCATION
- for patch_filename in $(ls -1 -v libeufin-bank-[0-9][0-9][0-9][0-9].sql); do
- patch_name=$(echo $patch_filename | cut -f1 -d.) # drops the final .sql
- echo Checking patch: "$patch_name"
- maybe_applied=$(check_patch_applied "$patch_name")
- if test -n "$maybe_applied"; then continue; fi
- # patch not applied, apply it.
- echo Patch $patch_name not applied, applying it.
- run_sql_file $patch_filename
- done
- cd - > /dev/null # cd to previous location.
-}
-
-if test $# -eq 0; then
- usage_and_exit
-fi
-
-while getopts ":d:l:hr" OPTION; do
- case "$OPTION" in
- d)
- DB_CONNECTION="$OPTARG" # only one required.
- ;;
- l)
- PATCHES_LOCATION="$OPTARG"
- ;;
- r)
- DROP="YES"
- ;;
- h)
- usage_and_exit
- ;;
- ?)
- fail 'Unrecognized command line option'
- ;;
- esac
-done
-
-# Checking required options.
-if test -z "${PATCHES_LOCATION:-}"; then
- # This value is substituted by GNU make at installation time.
- PATCHES_LOCATION=__BANK_STATIC_PATCHES_LOCATION__
-fi
-if test -z "${DB_CONNECTION:-}"; then
- fail "Required option '-d' was missing."
-fi
-
-run_sql_file $(get_patch_path "versioning.sql")
-if test "${DROP:-}" = "YES"; then
- maybe_applied=$(check_patch_applied "libeufin-bank-0001")
- if test -n "$maybe_applied"; then
- run_sql_file $(get_patch_path "libeufin-bank-drop.sql")
- else
- echo "Nothing to drop"
- fi
-fi
-iterate_over_patches
-run_sql_file $(get_patch_path "procedures.sql")
diff --git a/contrib/libeufin-bank.conf b/contrib/libeufin-bank.conf
index be718d9e..04be761f 100644
--- a/contrib/libeufin-bank.conf
+++ b/contrib/libeufin-bank.conf
@@ -14,4 +14,4 @@ CONFIG = postgresql:///libeufinbank
[libeufin-bankdb-postgres]
# Where are the SQL files to setup our tables?
-SQL_DIR = $DATADIR/sql/bank/
+SQL_DIR = $DATADIR/sql/libeufin-bank/
diff --git a/util/src/main/kotlin/TalerConfig.kt b/util/src/main/kotlin/TalerConfig.kt
index ce98c7f0..a0f5bf91 100644
--- a/util/src/main/kotlin/TalerConfig.kt
+++ b/util/src/main/kotlin/TalerConfig.kt
@@ -170,6 +170,14 @@ class TalerConfig {
return pathsub(entry.value)
}
+ fun requireValuePath(section: String, option: String): String {
+ val res = lookupValuePath(section, option)
+ if (res == null) {
+ throw TalerConfigError("expected path for section $section option $option")
+ }
+ return res
+ }
+
/**
* Create a string representation of the loaded configuration.
*/
@@ -314,10 +322,14 @@ class TalerConfig {
}
fun getTalerInstallPath(): String {
+ return getInstallPathFromBinary("taler-config")
+ }
+
+ fun getInstallPathFromBinary(name: String): String {
val pathEnv = System.getenv("PATH")
val paths = pathEnv.split(":")
for (p in paths) {
- val possiblePath = Paths.get(p, "taler-config").toString()
+ val possiblePath = Paths.get(p, name).toString()
if (File(possiblePath).exists()) {
return Paths.get(p, "..").toRealPath().toString()
}
diff --git a/util/src/test/kotlin/TalerConfigTest.kt b/util/src/test/kotlin/TalerConfigTest.kt
index f5d0dd50..4587c535 100644
--- a/util/src/test/kotlin/TalerConfigTest.kt
+++ b/util/src/test/kotlin/TalerConfigTest.kt
@@ -18,6 +18,7 @@
*/
import org.junit.Test
+import java.nio.file.FileSystems
import kotlin.test.assertEquals
class TalerConfigTest {