diff options
author | Florian Dold <florian@dold.me> | 2023-09-24 17:00:22 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-09-24 17:00:22 +0200 |
commit | 58b39bd5f70c3edc3594b0571cd6b5575ed9512e (patch) | |
tree | 4234cf7895178bc9a539d55aea079dfee98428aa | |
parent | 12015ed6dd0e1e91c9ce400ba332ee0bba9a7e42 (diff) | |
download | libeufin-58b39bd5f70c3edc3594b0571cd6b5575ed9512e.tar.gz libeufin-58b39bd5f70c3edc3594b0571cd6b5575ed9512e.tar.bz2 libeufin-58b39bd5f70c3edc3594b0571cd6b5575ed9512e.zip |
native dbinit
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt | 6 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Database.kt | 72 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 34 | ||||
-rw-r--r-- | bank/src/test/kotlin/Common.kt | 20 | ||||
-rw-r--r-- | bank/src/test/kotlin/TalerApiTest.kt | 6 | ||||
-rwxr-xr-x | contrib/libeufin-bank-dbinit | 107 | ||||
-rw-r--r-- | contrib/libeufin-bank.conf | 2 | ||||
-rw-r--r-- | util/src/main/kotlin/TalerConfig.kt | 14 | ||||
-rw-r--r-- | util/src/test/kotlin/TalerConfigTest.kt | 1 |
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 { |