From f5cde929a435a973b0d29e28484c1c743e1eb48c Mon Sep 17 00:00:00 2001 From: vityaman Date: Thu, 25 Apr 2024 17:49:12 +0300 Subject: [PATCH] #86 Added liquibase migrations at startup --- .../app/spring/storage/SpringMigration.kt | 28 +++++++++++++++++++ .../storage/migration/LiquibaseMigration.kt | 28 +++++++++++++++++++ botalka/src/main/resources/application.yml | 6 ++++ .../database/{schema.sql => changelog.sql} | 7 +++++ .../app/spring/BotalkaApplicationTests.kt | 4 +-- .../botalka/app/spring/BotalkaTestSuite.kt | 5 ++++ .../app/spring/storage/DatabaseContainer.kt | 15 +++++----- .../storage/DatabaseContainerInitializer.kt | 3 ++ .../kotlin/lms.conventions.jooq.gradle.kts | 2 +- .../kotlin/lms.conventions.spring.gradle.kts | 4 +++ 10 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/storage/SpringMigration.kt create mode 100644 botalka/src/main/kotlin/ru/vityaman/lms/botalka/storage/migration/LiquibaseMigration.kt rename botalka/src/main/resources/database/{schema.sql => changelog.sql} (93%) diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/storage/SpringMigration.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/storage/SpringMigration.kt new file mode 100644 index 0000000..700216e --- /dev/null +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/storage/SpringMigration.kt @@ -0,0 +1,28 @@ +package ru.vityaman.lms.botalka.app.spring.storage + +import org.springframework.beans.factory.annotation.Value +import org.springframework.jdbc.datasource.SingleConnectionDataSource +import org.springframework.stereotype.Component +import ru.vityaman.lms.botalka.storage.migration.LiquibaseMigration +import kotlin.io.path.Path + +@Component +class SpringMigration( + @Value("\${spring.liquibase.change-log}") + changelog: String, + + @Value("\${spring.datasource.url}") + url: String, + + @Value("\${spring.datasource.username}") + username: String, + + @Value("\${spring.datasource.password}") + password: String, +) { + init { + val suppressClose = false + SingleConnectionDataSource(url, username, password, suppressClose) + .use { LiquibaseMigration(Path(changelog), it).start() } + } +} diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/storage/migration/LiquibaseMigration.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/storage/migration/LiquibaseMigration.kt new file mode 100644 index 0000000..3437074 --- /dev/null +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/storage/migration/LiquibaseMigration.kt @@ -0,0 +1,28 @@ +package ru.vityaman.lms.botalka.storage.migration + +import liquibase.Liquibase +import liquibase.database.DatabaseFactory +import liquibase.database.jvm.JdbcConnection +import liquibase.resource.ClassLoaderResourceAccessor +import java.nio.file.Path +import javax.sql.DataSource +import kotlin.io.path.pathString + +class LiquibaseMigration( + private val changelog: Path, + private val source: DataSource, +) { + @Suppress("Deprecation") + fun start() { + val resources = ClassLoaderResourceAccessor(javaClass.classLoader) + println("Migration::start") + source.connection.use { connection -> + val database = DatabaseFactory.getInstance() + .findCorrectDatabaseImplementation(JdbcConnection(connection)) + Liquibase(changelog.pathString, resources, database).use { + it.update("development") + } + } + println("Migration::end") + } +} diff --git a/botalka/src/main/resources/application.yml b/botalka/src/main/resources/application.yml index a7e55f2..8ef88f6 100644 --- a/botalka/src/main/resources/application.yml +++ b/botalka/src/main/resources/application.yml @@ -5,6 +5,12 @@ spring: url: r2dbc:postgresql://database:5432/postgres username: postgres password: postgres + datasource: + url: jdbc:postgresql://database:5432/postgres + username: postgres + password: postgres + liquibase: + change-log: database/changelog.sql server: port: 8080 springdoc: diff --git a/botalka/src/main/resources/database/schema.sql b/botalka/src/main/resources/database/changelog.sql similarity index 93% rename from botalka/src/main/resources/database/schema.sql rename to botalka/src/main/resources/database/changelog.sql index 4b73712..bd50648 100644 --- a/botalka/src/main/resources/database/schema.sql +++ b/botalka/src/main/resources/database/changelog.sql @@ -1,5 +1,9 @@ +--liquibase formatted sql + +--changeset vityaman:init CREATE SCHEMA lms; +--changeset vityaman:users CREATE DOMAIN lms.alias AS VARCHAR(32) CHECK (VALUE ~ '[a-zA-Z]{3,31}'); @@ -24,6 +28,7 @@ CREATE TYPE lms.role AS ENUM ( 'student' ); +--changeset vityaman:promotions CREATE TYPE lms.promotion_request_status AS ENUM ( 'created', 'rejected', @@ -40,6 +45,7 @@ CREATE TABLE lms.promotion_request ( UNIQUE (user_id, role) ); +--changeset vityaman:homeworks CREATE DOMAIN lms.score AS smallint CHECK (0 < VALUE AND VALUE <= 2000); @@ -53,6 +59,7 @@ CREATE TABLE lms.homework ( creation_moment timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP ); +--changeset vityaman:events CREATE SEQUENCE lms.event_id_seq AS integer START 1; CREATE TABLE lms.homework_submission ( diff --git a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaApplicationTests.kt b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaApplicationTests.kt index 33967c5..102afdc 100644 --- a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaApplicationTests.kt +++ b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaApplicationTests.kt @@ -1,10 +1,8 @@ package ru.vityaman.lms.botalka.app.spring import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest -@SpringBootTest -class BotalkaApplicationTests { +class BotalkaApplicationTests : BotalkaTestSuite() { @Test fun contextLoads() { // Okay diff --git a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaTestSuite.kt b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaTestSuite.kt index 401f3c2..404b944 100644 --- a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaTestSuite.kt +++ b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/BotalkaTestSuite.kt @@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import ru.vityaman.lms.botalka.app.spring.storage.DatabaseContainerInitializer +import ru.vityaman.lms.botalka.app.spring.storage.SpringMigration import ru.vityaman.lms.botalka.storage.jooq.Lms.Companion.LMS import ru.vityaman.lms.botalka.storage.jooq.util.toFlux @@ -22,8 +23,12 @@ abstract class BotalkaTestSuite { @Autowired private lateinit var database: DSLContext + @Autowired + private lateinit var migration: SpringMigration + @AfterEach fun afterEach(): Unit = runBlocking { + migration.let { } for (table in LMS.tables) { launch { database diff --git a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainer.kt b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainer.kt index 4b31d1a..9003458 100644 --- a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainer.kt +++ b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainer.kt @@ -1,11 +1,9 @@ package ru.vityaman.lms.botalka.app.spring.storage import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.utility.MountableFile class DatabaseContainer : AutoCloseable { private val postgres = PostgreSQLContainer("postgres") - .withCopyToContainer(SCHEMA_SCRIPT, INIT_SQL) val r2dbcUrl: String get() { @@ -15,6 +13,14 @@ class DatabaseContainer : AutoCloseable { return "r2dbc:postgresql://$host:$port/$name" } + val jdbcUrl: String + get() { + val host = postgres.host + val port = postgres.firstMappedPort + val name = postgres.databaseName + return "jdbc:postgresql://$host:$port/$name" + } + val username: String get() = postgres.username val password: String get() = postgres.password @@ -29,11 +35,6 @@ class DatabaseContainer : AutoCloseable { } companion object { - private const val INIT_SQL = "/docker-entrypoint-initdb.d/init.sql" - - private val SCHEMA_SCRIPT = - MountableFile.forClasspathResource("database/schema.sql") - val instance: DatabaseContainer by lazy { DatabaseContainer() } } } diff --git a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainerInitializer.kt b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainerInitializer.kt index 0ec7aa5..61b9e6a 100644 --- a/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainerInitializer.kt +++ b/botalka/src/test/kotlin/ru/vityaman/lms/botalka/app/spring/storage/DatabaseContainerInitializer.kt @@ -13,6 +13,9 @@ class DatabaseContainerInitializer : "spring.r2dbc.url=${database.r2dbcUrl}", "spring.r2dbc.username=${database.username}", "spring.r2dbc.password=${database.password}", + "spring.datasource.url=${database.jdbcUrl}", + "spring.datasource.username=${database.username}", + "spring.datasource.password=${database.password}", ).applyTo(ctx.environment) } } diff --git a/buildSrc/src/main/kotlin/lms.conventions.jooq.gradle.kts b/buildSrc/src/main/kotlin/lms.conventions.jooq.gradle.kts index 9ac64c4..f3a7689 100644 --- a/buildSrc/src/main/kotlin/lms.conventions.jooq.gradle.kts +++ b/buildSrc/src/main/kotlin/lms.conventions.jooq.gradle.kts @@ -35,7 +35,7 @@ jooq { executions { create("main") { val jdbcUrl = { - val schemaSql = "$projectDir/src/main/resources/database/schema.sql" + val schemaSql = "$projectDir/src/main/resources/database/changelog.sql" val protocol = "jdbc:tc:postgresql:16" val tmpfs = "TC_TMPFS=/testtmpfs:rw&" val script = "TC_INITSCRIPT=file:$schemaSql" diff --git a/buildSrc/src/main/kotlin/lms.conventions.spring.gradle.kts b/buildSrc/src/main/kotlin/lms.conventions.spring.gradle.kts index b68ad76..89cc020 100644 --- a/buildSrc/src/main/kotlin/lms.conventions.spring.gradle.kts +++ b/buildSrc/src/main/kotlin/lms.conventions.spring.gradle.kts @@ -19,6 +19,10 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") runtimeOnly("org.postgresql:r2dbc-postgresql") + implementation("org.liquibase:liquibase-core") + implementation("org.springframework.boot:spring-boot-starter-jdbc") + runtimeOnly("org.postgresql:postgresql") + implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("io.micrometer:micrometer-registry-prometheus")