2019-01-28 19:18:52 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"compress/gzip"
2019-12-15 12:51:28 +03:00
"context"
2019-01-28 19:18:52 +03:00
"database/sql"
"fmt"
"io/ioutil"
"os"
"path"
2020-04-14 16:53:34 +03:00
"path/filepath"
2019-01-28 19:18:52 +03:00
"regexp"
"sort"
2019-05-06 02:42:29 +03:00
"strings"
2019-01-28 19:18:52 +03:00
"testing"
2019-04-07 03:25:14 +03:00
"code.gitea.io/gitea/integrations"
2019-01-28 19:18:52 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/migrations"
2019-04-17 19:06:35 +03:00
"code.gitea.io/gitea/modules/base"
2019-08-15 15:07:28 +03:00
"code.gitea.io/gitea/modules/charset"
2021-06-26 14:28:55 +03:00
"code.gitea.io/gitea/modules/git"
2019-01-28 19:18:52 +03:00
"code.gitea.io/gitea/modules/setting"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2019-01-28 19:18:52 +03:00
"github.com/stretchr/testify/assert"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2019-01-28 19:18:52 +03:00
)
var currentEngine * xorm . Engine
2019-11-26 02:21:37 +03:00
func initMigrationTest ( t * testing . T ) func ( ) {
deferFn := integrations . PrintCurrentTest ( t , 2 )
2019-04-17 19:06:35 +03:00
giteaRoot := base . SetupGiteaRoot ( )
2019-01-28 19:18:52 +03:00
if giteaRoot == "" {
2019-04-07 03:25:14 +03:00
integrations . Printf ( "Environment variable $GITEA_ROOT not set\n" )
2019-01-28 19:18:52 +03:00
os . Exit ( 1 )
}
setting . AppPath = path . Join ( giteaRoot , "gitea" )
if _ , err := os . Stat ( setting . AppPath ) ; err != nil {
2019-04-07 03:25:14 +03:00
integrations . Printf ( "Could not find gitea binary at %s\n" , setting . AppPath )
2019-01-28 19:18:52 +03:00
os . Exit ( 1 )
}
giteaConf := os . Getenv ( "GITEA_CONF" )
if giteaConf == "" {
2019-04-07 03:25:14 +03:00
integrations . Printf ( "Environment variable $GITEA_CONF not set\n" )
2019-01-28 19:18:52 +03:00
os . Exit ( 1 )
} else if ! path . IsAbs ( giteaConf ) {
setting . CustomConf = path . Join ( giteaRoot , giteaConf )
} else {
setting . CustomConf = giteaConf
}
setting . NewContext ( )
2020-04-14 16:53:34 +03:00
assert . True ( t , len ( setting . RepoRootPath ) != 0 )
2020-08-11 23:05:34 +03:00
assert . NoError ( t , util . RemoveAll ( setting . RepoRootPath ) )
2020-12-25 12:59:32 +03:00
assert . NoError ( t , util . CopyDir ( path . Join ( filepath . Dir ( setting . AppPath ) , "integrations/gitea-repositories-meta" ) , setting . RepoRootPath ) )
2020-04-14 16:53:34 +03:00
2021-06-26 14:28:55 +03:00
git . CheckLFSVersion ( )
2019-08-24 12:24:45 +03:00
setting . InitDBConfig ( )
2019-04-07 03:25:14 +03:00
setting . NewLogServices ( true )
2019-11-26 02:21:37 +03:00
return deferFn
2019-01-28 19:18:52 +03:00
}
func availableVersions ( ) ( [ ] string , error ) {
migrationsDir , err := os . Open ( "integrations/migration-test" )
if err != nil {
return nil , err
}
defer migrationsDir . Close ( )
2019-08-24 12:24:45 +03:00
versionRE , err := regexp . Compile ( "gitea-v(?P<version>.+)\\." + regexp . QuoteMeta ( setting . Database . Type ) + "\\.sql.gz" )
2019-01-28 19:18:52 +03:00
if err != nil {
return nil , err
}
filenames , err := migrationsDir . Readdirnames ( - 1 )
if err != nil {
return nil , err
}
versions := [ ] string { }
for _ , filename := range filenames {
if versionRE . MatchString ( filename ) {
substrings := versionRE . FindStringSubmatch ( filename )
versions = append ( versions , substrings [ 1 ] )
}
}
sort . Strings ( versions )
return versions , nil
}
func readSQLFromFile ( version string ) ( string , error ) {
2019-08-24 12:24:45 +03:00
filename := fmt . Sprintf ( "integrations/migration-test/gitea-v%s.%s.sql.gz" , version , setting . Database . Type )
2019-01-28 19:18:52 +03:00
if _ , err := os . Stat ( filename ) ; os . IsNotExist ( err ) {
return "" , nil
}
file , err := os . Open ( filename )
if err != nil {
return "" , err
}
defer file . Close ( )
gr , err := gzip . NewReader ( file )
if err != nil {
return "" , err
}
defer gr . Close ( )
bytes , err := ioutil . ReadAll ( gr )
if err != nil {
return "" , err
}
2019-08-15 15:07:28 +03:00
return string ( charset . RemoveBOMIfPresent ( bytes ) ) , nil
2019-01-28 19:18:52 +03:00
}
func restoreOldDB ( t * testing . T , version string ) bool {
data , err := readSQLFromFile ( version )
assert . NoError ( t , err )
if len ( data ) == 0 {
2019-08-24 12:24:45 +03:00
integrations . Printf ( "No db found to restore for %s version: %s\n" , setting . Database . Type , version )
2019-01-28 19:18:52 +03:00
return false
}
switch {
2019-08-24 12:24:45 +03:00
case setting . Database . UseSQLite3 :
2020-08-11 23:05:34 +03:00
util . Remove ( setting . Database . Path )
2019-08-24 12:24:45 +03:00
err := os . MkdirAll ( path . Dir ( setting . Database . Path ) , os . ModePerm )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
2020-02-27 02:51:37 +03:00
db , err := sql . Open ( "sqlite3" , fmt . Sprintf ( "file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate" , setting . Database . Path , setting . Database . Timeout ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
defer db . Close ( )
_ , err = db . Exec ( data )
assert . NoError ( t , err )
db . Close ( )
2019-08-24 12:24:45 +03:00
case setting . Database . UseMySQL :
2019-01-28 19:18:52 +03:00
db , err := sql . Open ( "mysql" , fmt . Sprintf ( "%s:%s@tcp(%s)/" ,
2019-08-24 12:24:45 +03:00
setting . Database . User , setting . Database . Passwd , setting . Database . Host ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
defer db . Close ( )
2019-08-24 12:24:45 +03:00
_ , err = db . Exec ( fmt . Sprintf ( "DROP DATABASE IF EXISTS %s" , setting . Database . Name ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
2019-08-24 12:24:45 +03:00
_ , err = db . Exec ( fmt . Sprintf ( "CREATE DATABASE IF NOT EXISTS %s" , setting . Database . Name ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
2020-09-18 04:36:14 +03:00
db . Close ( )
2019-01-28 19:18:52 +03:00
db , err = sql . Open ( "mysql" , fmt . Sprintf ( "%s:%s@tcp(%s)/%s?multiStatements=true" ,
2019-08-24 12:24:45 +03:00
setting . Database . User , setting . Database . Passwd , setting . Database . Host , setting . Database . Name ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
defer db . Close ( )
_ , err = db . Exec ( data )
assert . NoError ( t , err )
db . Close ( )
2019-08-24 12:24:45 +03:00
case setting . Database . UsePostgreSQL :
2019-01-28 19:18:52 +03:00
db , err := sql . Open ( "postgres" , fmt . Sprintf ( "postgres://%s:%s@%s/?sslmode=%s" ,
2019-08-24 12:24:45 +03:00
setting . Database . User , setting . Database . Passwd , setting . Database . Host , setting . Database . SSLMode ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
defer db . Close ( )
2019-08-24 12:24:45 +03:00
_ , err = db . Exec ( fmt . Sprintf ( "DROP DATABASE IF EXISTS %s" , setting . Database . Name ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
2019-08-24 12:24:45 +03:00
_ , err = db . Exec ( fmt . Sprintf ( "CREATE DATABASE %s" , setting . Database . Name ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
db . Close ( )
2020-01-20 18:45:14 +03:00
// Check if we need to setup a specific schema
if len ( setting . Database . Schema ) != 0 {
db , err = sql . Open ( "postgres" , fmt . Sprintf ( "postgres://%s:%s@%s/%s?sslmode=%s" ,
setting . Database . User , setting . Database . Passwd , setting . Database . Host , setting . Database . Name , setting . Database . SSLMode ) )
if ! assert . NoError ( t , err ) {
return false
}
2020-09-18 04:36:14 +03:00
defer db . Close ( )
2020-01-20 18:45:14 +03:00
schrows , err := db . Query ( fmt . Sprintf ( "SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'" , setting . Database . Schema ) )
if ! assert . NoError ( t , err ) || ! assert . NotEmpty ( t , schrows ) {
return false
}
if ! schrows . Next ( ) {
// Create and setup a DB schema
_ , err = db . Exec ( fmt . Sprintf ( "CREATE SCHEMA %s" , setting . Database . Schema ) )
assert . NoError ( t , err )
}
schrows . Close ( )
// Make the user's default search path the created schema; this will affect new connections
_ , err = db . Exec ( fmt . Sprintf ( ` ALTER USER "%s" SET search_path = %s ` , setting . Database . User , setting . Database . Schema ) )
assert . NoError ( t , err )
db . Close ( )
}
2019-01-28 19:18:52 +03:00
db , err = sql . Open ( "postgres" , fmt . Sprintf ( "postgres://%s:%s@%s/%s?sslmode=%s" ,
2019-08-24 12:24:45 +03:00
setting . Database . User , setting . Database . Passwd , setting . Database . Host , setting . Database . Name , setting . Database . SSLMode ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
defer db . Close ( )
_ , err = db . Exec ( data )
assert . NoError ( t , err )
db . Close ( )
2019-08-24 12:24:45 +03:00
case setting . Database . UseMSSQL :
host , port := setting . ParseMSSQLHostPort ( setting . Database . Host )
2019-01-28 19:18:52 +03:00
db , err := sql . Open ( "mssql" , fmt . Sprintf ( "server=%s; port=%s; database=%s; user id=%s; password=%s;" ,
2019-08-24 12:24:45 +03:00
host , port , "master" , setting . Database . User , setting . Database . Passwd ) )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
defer db . Close ( )
2019-07-26 07:10:20 +03:00
_ , err = db . Exec ( "DROP DATABASE IF EXISTS [gitea]" )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
2019-05-06 02:42:29 +03:00
statements := strings . Split ( data , "\nGO\n" )
for _ , statement := range statements {
2019-07-26 07:10:20 +03:00
if len ( statement ) > 5 && statement [ : 5 ] == "USE [" {
dbname := statement [ 5 : len ( statement ) - 1 ]
db . Close ( )
db , err = sql . Open ( "mssql" , fmt . Sprintf ( "server=%s; port=%s; database=%s; user id=%s; password=%s;" ,
2019-08-24 12:24:45 +03:00
host , port , dbname , setting . Database . User , setting . Database . Passwd ) )
2019-07-26 07:10:20 +03:00
assert . NoError ( t , err )
defer db . Close ( )
}
2019-05-06 02:42:29 +03:00
_ , err = db . Exec ( statement )
assert . NoError ( t , err , "Failure whilst running: %s\nError: %v" , statement , err )
}
2019-01-28 19:18:52 +03:00
db . Close ( )
}
return true
}
func wrappedMigrate ( x * xorm . Engine ) error {
currentEngine = x
return migrations . Migrate ( x )
}
func doMigrationTest ( t * testing . T , version string ) {
2019-11-26 02:21:37 +03:00
defer integrations . PrintCurrentTest ( t ) ( )
2019-08-24 12:24:45 +03:00
integrations . Printf ( "Performing migration test for %s version: %s\n" , setting . Database . Type , version )
2019-01-28 19:18:52 +03:00
if ! restoreOldDB ( t , version ) {
return
}
setting . NewXORMLogService ( false )
2020-09-15 21:44:52 +03:00
err := models . NewEngine ( context . Background ( ) , wrappedMigrate )
2019-01-28 19:18:52 +03:00
assert . NoError ( t , err )
currentEngine . Close ( )
2020-09-07 00:52:01 +03:00
beans , _ := models . NamesToBean ( )
err = models . NewEngine ( context . Background ( ) , func ( x * xorm . Engine ) error {
currentEngine = x
return migrations . RecreateTables ( beans ... ) ( x )
} )
assert . NoError ( t , err )
currentEngine . Close ( )
// We do this a second time to ensure that there is not a problem with retained indices
err = models . NewEngine ( context . Background ( ) , func ( x * xorm . Engine ) error {
currentEngine = x
return migrations . RecreateTables ( beans ... ) ( x )
} )
assert . NoError ( t , err )
currentEngine . Close ( )
2019-01-28 19:18:52 +03:00
}
func TestMigrations ( t * testing . T ) {
2019-11-26 02:21:37 +03:00
defer initMigrationTest ( t ) ( )
2019-01-28 19:18:52 +03:00
2019-08-24 12:24:45 +03:00
dialect := setting . Database . Type
2019-01-28 19:18:52 +03:00
versions , err := availableVersions ( )
assert . NoError ( t , err )
if len ( versions ) == 0 {
2019-04-07 03:25:14 +03:00
integrations . Printf ( "No old database versions available to migration test for %s\n" , dialect )
2019-01-28 19:18:52 +03:00
return
}
2019-04-07 03:25:14 +03:00
integrations . Printf ( "Preparing to test %d migrations for %s\n" , len ( versions ) , dialect )
2019-01-28 19:18:52 +03:00
for _ , version := range versions {
2019-04-07 03:25:14 +03:00
t . Run ( fmt . Sprintf ( "Migrate-%s-%s" , dialect , version ) , func ( t * testing . T ) {
doMigrationTest ( t , version )
} )
2019-01-28 19:18:52 +03:00
}
}