feat: add db-migrate tool for running SQL migrations
- Create db-migrate binary that runs all .up.sql migration files - Add Dockerfile.migrate for building the migration image - Add migration job to Woodpecker CI pipeline - Image will be pushed to registry.nxtgauge.com:5000/nxtgauge-db-migrate
This commit is contained in:
parent
019613aa82
commit
dade35b328
5 changed files with 136 additions and 1 deletions
|
|
@ -46,3 +46,31 @@ steps:
|
||||||
skip_tls_verify: true
|
skip_tls_verify: true
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
|
---
|
||||||
|
when:
|
||||||
|
branch: [main, high-performance]
|
||||||
|
event: push
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-and-push-migrate
|
||||||
|
image: woodpeckerci/plugin-kaniko:2.1.1
|
||||||
|
settings:
|
||||||
|
registry:
|
||||||
|
from_secret: REGISTRY_HOSTPORT
|
||||||
|
repo: nxtgauge-db-migrate
|
||||||
|
dockerfile: Dockerfile.migrate
|
||||||
|
context: .
|
||||||
|
tags:
|
||||||
|
- ${CI_COMMIT_SHA}
|
||||||
|
- latest
|
||||||
|
- high-performance-latest
|
||||||
|
username:
|
||||||
|
from_secret: REGISTRY_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
|
insecure: true
|
||||||
|
insecure_pull: true
|
||||||
|
skip_tls_verify: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
cache: false
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ members = [
|
||||||
"crates/email",
|
"crates/email",
|
||||||
"apps/cron",
|
"apps/cron",
|
||||||
"apps/employees",
|
"apps/employees",
|
||||||
"apps/payments"
|
"apps/payments",
|
||||||
|
"crates/db-migrate"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
22
Dockerfile.migrate
Normal file
22
Dockerfile.migrate
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
FROM rust:1.75-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev pkgconfig openssl-dev
|
||||||
|
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY crates/db-migrate ./crates/db-migrate
|
||||||
|
COPY crates/db ./crates/db
|
||||||
|
COPY crates/cache ./crates/cache
|
||||||
|
COPY crates/email ./crates/email
|
||||||
|
|
||||||
|
WORKDIR /app/crates/db-migrate
|
||||||
|
RUN cargo build --release --bin db-migrate
|
||||||
|
|
||||||
|
FROM alpine:3.19
|
||||||
|
RUN apk add --no-cache ca-certificates libpq
|
||||||
|
|
||||||
|
COPY --from=builder /app/crates/db-migrate/target/release/db-migrate /usr/local/bin/
|
||||||
|
COPY crates/db/migrations /migrations
|
||||||
|
|
||||||
|
ENTRYPOINT ["db-migrate"]
|
||||||
12
crates/db-migrate/Cargo.toml
Normal file
12
crates/db-migrate/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "db-migrate"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sqlx = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
serde = { workspace = true }
|
||||||
72
crates/db-migrate/src/main.rs
Normal file
72
crates/db-migrate/src/main.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use std::path::Path;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::new(
|
||||||
|
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
|
||||||
|
))
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let database_url = std::env::var("DATABASE_URL")
|
||||||
|
.context("DATABASE_URL must be set")?;
|
||||||
|
|
||||||
|
tracing::info!("Connecting to database...");
|
||||||
|
let pool = sqlx::postgres::PgPoolOptions::new()
|
||||||
|
.max_connections(1)
|
||||||
|
.connect(&database_url)
|
||||||
|
.await
|
||||||
|
.context("Failed to connect to database")?;
|
||||||
|
tracing::info!("Connected to database");
|
||||||
|
|
||||||
|
let migrations_dir = std::env::var("MIGRATIONS_DIR")
|
||||||
|
.unwrap_or_else(|_| "/migrations".to_string());
|
||||||
|
|
||||||
|
run_migrations(&pool, &migrations_dir).await?;
|
||||||
|
|
||||||
|
tracing::info!("All migrations completed successfully!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_migrations(pool: &sqlx::PgPool, migrations_dir: &str) -> Result<()> {
|
||||||
|
let migrations_path = Path::new(migrations_dir);
|
||||||
|
|
||||||
|
if !migrations_path.exists() {
|
||||||
|
tracing::warn!("Migrations directory does not exist: {}", migrations_dir);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entries: Vec<_> = std::fs::read_dir(migrations_path)?
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| {
|
||||||
|
let name = e.file_name();
|
||||||
|
let name_str = name.to_string_lossy();
|
||||||
|
name_str.ends_with(".up.sql")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
entries.sort_by_key(|e| e.file_name());
|
||||||
|
|
||||||
|
tracing::info!("Found {} migration files", entries.len());
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let file_path = entry.path();
|
||||||
|
|
||||||
|
tracing::info!("Applying migration: {}", file_name.to_string_lossy());
|
||||||
|
|
||||||
|
let sql = std::fs::read_to_string(&file_path)
|
||||||
|
.with_context(|| format!("Failed to read migration: {}", file_name.to_string_lossy()))?;
|
||||||
|
|
||||||
|
sqlx::raw_sql(&sql)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to execute migration: {}", file_name.to_string_lossy()))?;
|
||||||
|
|
||||||
|
tracing::info!("Applied migration: {}", file_name.to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue