mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 23:56:37 +08:00
205 lines
5.2 KiB
Go
205 lines
5.2 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package migrator
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pressly/goose/v3"
|
|
)
|
|
|
|
func init() {
|
|
goose.AddMigrationContext(up202606050001, down202606050001)
|
|
}
|
|
|
|
func up202606050001(ctx context.Context, tx *sql.Tx) error {
|
|
dialect := gooseDialect()
|
|
if dialect == dialectSqlite {
|
|
if err := fixSqliteGooseDbVersion(ctx, tx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
tableExistsQuery := tableExistsSQL(dialect)
|
|
|
|
checkTable := func(name string) (bool, error) {
|
|
var count int
|
|
err := tx.QueryRowContext(ctx, tableExistsQuery, name).Scan(&count)
|
|
return count > 0, err
|
|
}
|
|
|
|
// Check if this is a legacy database (by checking if the "options" table exists).
|
|
// On a clean install, "options" won't exist, so this will be a quick no-op.
|
|
hasOptions, err := checkTable("options")
|
|
if err != nil {
|
|
return fmt.Errorf("check table options failed: %w", err)
|
|
}
|
|
|
|
if !hasOptions {
|
|
// Clean install or already migrated, no action needed.
|
|
return nil
|
|
}
|
|
|
|
// Helper to drop table if it exists
|
|
dropTable := func(tableName string) error {
|
|
dropSQL := fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)
|
|
if dialect == dialectPostgres {
|
|
dropSQL += cascadeSuffix
|
|
}
|
|
if _, err := tx.ExecContext(ctx, dropSQL); err != nil {
|
|
return fmt.Errorf("drop table %s failed: %w", tableName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 1. Drop legacy observability and log tables to free space and avoid conflict
|
|
obsPrefixes := []string{
|
|
"node_system_profiles",
|
|
"node_health_events",
|
|
"node_access_logs_",
|
|
"node_observation_openresties_",
|
|
"node_observation_frpcs_",
|
|
"node_observation_frps_",
|
|
"node_metric_snapshots_",
|
|
"node_request_reports_",
|
|
}
|
|
|
|
for _, prefix := range obsPrefixes {
|
|
if err := dropTablesWithPrefix(ctx, tx, tablesWithPrefixSQL(dialect), prefix, dropTable); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// 2. Rename custom and platform legacy tables to legacy_ prefix
|
|
tablesToRename := []string{
|
|
"users",
|
|
"auth_sources",
|
|
"external_accounts",
|
|
"options",
|
|
"origins",
|
|
"apply_logs",
|
|
"proxy_routes",
|
|
"nodes",
|
|
"waf_rule_groups",
|
|
"waf_rule_group_bindings",
|
|
"waf_ip_groups",
|
|
"tls_certificates",
|
|
"managed_domains",
|
|
"dns_accounts",
|
|
"acme_accounts",
|
|
"config_versions",
|
|
"pages_projects",
|
|
"pages_deployments",
|
|
"pages_deployment_files",
|
|
}
|
|
|
|
for _, oldName := range tablesToRename {
|
|
if err := renameLegacyTable(ctx, tx, oldName, checkTable, dropTable); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func dropTablesWithPrefix(ctx context.Context, tx *sql.Tx, query, prefix string, dropTable func(string) error) error {
|
|
rows, err := tx.QueryContext(ctx, query, prefix+"%")
|
|
if err != nil {
|
|
return fmt.Errorf("query legacy tables for prefix %s failed: %w", prefix, err)
|
|
}
|
|
defer func() {
|
|
_ = rows.Close()
|
|
}()
|
|
|
|
var tables []string
|
|
for rows.Next() {
|
|
var name string
|
|
if err := rows.Scan(&name); err != nil {
|
|
return err
|
|
}
|
|
tables = append(tables, name)
|
|
}
|
|
|
|
for _, table := range tables {
|
|
if err := dropTable(table); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func renameLegacyTable(ctx context.Context, tx *sql.Tx, oldName string, checkTable func(string) (bool, error), dropTable func(string) error) error {
|
|
exists, err := checkTable(oldName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
newName := "legacy_" + oldName
|
|
newExists, err := checkTable(newName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newExists {
|
|
if err := dropTable(newName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
renameSQL := fmt.Sprintf("ALTER TABLE %s RENAME TO %s", oldName, newName)
|
|
if _, err := tx.ExecContext(ctx, renameSQL); err != nil {
|
|
return fmt.Errorf("rename table %s to %s failed: %w", oldName, newName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func down202606050001(_ context.Context, _ *sql.Tx) error {
|
|
// Down migration is a no-op as the legacy DB is rolled forward during migration.
|
|
return nil
|
|
}
|
|
|
|
func fixSqliteGooseDbVersion(ctx context.Context, tx *sql.Tx) error {
|
|
var createSQL string
|
|
err := tx.QueryRowContext(ctx, "SELECT sql FROM sqlite_master WHERE type='table' AND name='goose_db_version'").Scan(&createSQL)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
if strings.Contains(strings.ToUpper(createSQL), "AUTOINCREMENT") {
|
|
return nil // Already upgraded
|
|
}
|
|
|
|
if _, err := tx.ExecContext(ctx, "ALTER TABLE goose_db_version RENAME TO old_goose_db_version"); err != nil {
|
|
return fmt.Errorf("rename goose_db_version failed: %w", err)
|
|
}
|
|
|
|
newTableSQL := `CREATE TABLE goose_db_version (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
version_id INTEGER NOT NULL,
|
|
is_applied INTEGER NOT NULL,
|
|
tstamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);`
|
|
if _, err := tx.ExecContext(ctx, newTableSQL); err != nil {
|
|
return fmt.Errorf("create new goose_db_version failed: %w", err)
|
|
}
|
|
|
|
copySQL := `INSERT INTO goose_db_version (id, version_id, is_applied, tstamp)
|
|
SELECT id, version_id, is_applied, tstamp FROM old_goose_db_version;`
|
|
if _, err := tx.ExecContext(ctx, copySQL); err != nil {
|
|
return fmt.Errorf("copy goose_db_version data failed: %w", err)
|
|
}
|
|
|
|
if _, err := tx.ExecContext(ctx, "DROP TABLE old_goose_db_version"); err != nil {
|
|
return fmt.Errorf("drop old_goose_db_version failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|