Files
2026-07-13 12:32:21 +08:00

222 lines
5.8 KiB
Lua

local log = require "kong.cmd.utils.log"
local MIGRATIONS_MUTEX_KEY = "migrations"
local NOT_LEADER_MSG = "aborted: another node is performing database changes"
local NEEDS_BOOTSTRAP_MSG = "Database needs bootstrapping or is older than Kong 1.0.\n\n" ..
"To start a new installation from scratch, run 'kong migrations bootstrap'.\n\n" ..
"To migrate from a version older than 1.0, migrated to Kong 1.5.0 first. \n" ..
"If you still have 'apis' entities, you can convert them to Routes and Services\n" ..
"using the 'kong migrations migrate-apis' command in Kong 1.5.0.\n\n"
local function check_state(schema_state)
if not schema_state:is_up_to_date() then
if schema_state.needs_bootstrap then
error(NEEDS_BOOTSTRAP_MSG)
end
if schema_state.new_migrations then
error("New migrations available; run 'kong migrations up' to proceed")
end
end
end
local function bootstrap(schema_state, db, ttl)
if schema_state.needs_bootstrap then
log("Bootstrapping database...")
assert(db:schema_bootstrap())
else
log("Database already bootstrapped")
return
end
local opts = {
ttl = ttl,
no_wait = true, -- exit the mutex if another node acquired it
}
local ok, err = db:cluster_mutex(MIGRATIONS_MUTEX_KEY, opts, function()
assert(db:run_migrations(schema_state.new_migrations, {
run_up = true,
run_teardown = true,
}))
log("Database is up-to-date")
end)
if err then
error(err)
end
if not ok then
log(NOT_LEADER_MSG)
end
end
local function up(schema_state, db, opts)
if schema_state.needs_bootstrap then
-- fresh install: must bootstrap (which will run migrations up)
error("Cannot run migrations: " .. NEEDS_BOOTSTRAP_MSG)
end
-- see #6105 for background, this is a workaround that gives a better
-- error message (one w/o the long stacktrace) when the pending
-- migration checks failed
if not opts.force and schema_state.pending_migrations then
error("Database has pending migrations; run 'kong migrations finish'")
end
local ok, err = db:cluster_mutex(MIGRATIONS_MUTEX_KEY, opts, function()
schema_state = assert(db:schema_state())
if not opts.force and schema_state.pending_migrations then
error("Database has pending migrations; run 'kong migrations finish'")
end
if opts.force and schema_state.executed_migrations then
log.debug("forcing re-execution of these migrations:\n%s",
schema_state.executed_migrations)
assert(db:run_migrations(schema_state.executed_migrations, {
run_up = true,
run_teardown = true,
skip_teardown_migrations = schema_state.pending_migrations
}))
schema_state = assert(db:schema_state())
if schema_state.pending_migrations then
log("\nDatabase has pending migrations; run 'kong migrations finish' when ready")
return
end
end
if not schema_state.new_migrations then
if not opts.force then
log("Database is already up-to-date")
end
return
end
log.debug("migrations to run:\n%s", schema_state.new_migrations)
assert(db:run_migrations(schema_state.new_migrations, {
run_up = true,
}))
schema_state = assert(db:schema_state())
if schema_state.pending_migrations then
log("\nDatabase has pending migrations; run 'kong migrations finish' when ready")
return
end
end)
if err then
error(err)
end
if not ok then
log(NOT_LEADER_MSG)
end
return ok
end
local function finish(schema_state, db, opts)
if schema_state.needs_bootstrap then
error("Cannot run migrations: " .. NEEDS_BOOTSTRAP_MSG)
end
opts.no_wait = true -- exit the mutex if another node acquired it
local ok, err = db:cluster_mutex(MIGRATIONS_MUTEX_KEY, opts, function()
local schema_state = assert(db:schema_state())
if opts.force and schema_state.executed_migrations then
assert(db:run_migrations(schema_state.executed_migrations, {
run_up = true,
run_teardown = true,
}))
schema_state = assert(db:schema_state())
end
if schema_state.pending_migrations then
log.debug("pending migrations to finish:\n%s",
schema_state.pending_migrations)
assert(db:run_migrations(schema_state.pending_migrations, {
run_teardown = true,
}))
schema_state = assert(db:schema_state())
end
if schema_state.new_migrations then
log("\nNew migrations available; run 'kong migrations up' to proceed")
return
end
if not opts.force and not schema_state.pending_migrations then
log("No pending migrations to finish")
end
return
end)
if err then
error(err)
end
if not ok then
log(NOT_LEADER_MSG)
end
end
local function reset(schema_state, db, ttl)
if schema_state.needs_bootstrap then
log("Database not bootstrapped, nothing to reset")
return false
end
local opts = {
ttl = ttl,
no_wait = true,
no_cleanup = true,
}
local ok, err = db:cluster_mutex(MIGRATIONS_MUTEX_KEY, opts, function()
log("Resetting database...")
assert(db:schema_reset())
log("Database successfully reset")
end)
if err then
-- failed to acquire locks - maybe locks table was dropped?
log.error(err .. " - retrying without cluster lock")
log("Resetting database...")
-- ROLLBACK in order to solve this error
-- ERROR: current transaction is aborted, commands ignored until end of transaction block
assert(db.connector:query("ROLLBACK;"))
assert(db:schema_reset())
log("Database successfully reset")
return true
end
if not ok then
log(NOT_LEADER_MSG)
return false
end
return true
end
return {
up = up,
reset = reset,
finish = finish,
bootstrap = bootstrap,
check_state = check_state,
NEEDS_BOOTSTRAP_MSG = NEEDS_BOOTSTRAP_MSG,
}