Reimplement dump and implment restore command

This commit is contained in:
Lunny Xiao 2017-11-15 17:15:34 +08:00
parent bb8014885f
commit 971b13c367
No known key found for this signature in database
GPG Key ID: C3B7C91B632F738A
4 changed files with 252 additions and 23 deletions

View File

@ -1,5 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2016 The Gitea Authors. All rights reserved.
// Copyright 2017 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.
@ -44,10 +44,6 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Value: os.TempDir(),
Usage: "Temporary dir path",
},
cli.StringFlag{
Name: "database, d",
Usage: "Specify the database SQL syntax",
},
},
}
@ -79,23 +75,15 @@ func runDump(ctx *cli.Context) error {
os.Setenv("TMPDIR", tmpWorkDir)
}
reposDump := path.Join(tmpWorkDir, "gitea-repo.zip")
dbDump := path.Join(tmpWorkDir, "gitea-db.sql")
dbDump := path.Join(tmpWorkDir, "database")
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
zip.Verbose = ctx.Bool("verbose")
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
log.Fatalf("Failed to dump local repositories: %v", err)
if err := os.MkdirAll(dbDump, os.ModePerm); err != nil {
log.Fatalf("Failed to create database dir: %v", err)
}
targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != models.DbCfg.Type {
log.Printf("Dumping database %s => %s...", models.DbCfg.Type, targetDBType)
} else {
log.Printf("Dumping database...")
}
if err := models.DumpDatabase(dbDump, targetDBType); err != nil {
log.Printf("Dumping database ...")
if err := models.DumpDatabaseFixtures(dbDump); err != nil {
log.Fatalf("Failed to dump database: %v", err)
}
@ -106,11 +94,12 @@ func runDump(ctx *cli.Context) error {
log.Fatalf("Failed to create %s: %v", fileName, err)
}
if err := z.AddFile("gitea-repo.zip", reposDump); err != nil {
if err := z.AddDir("repositories", setting.RepoRootPath); err != nil {
log.Fatalf("Failed to include gitea-repo.zip: %v", err)
}
if err := z.AddFile("gitea-db.sql", dbDump); err != nil {
log.Fatalf("Failed to include gitea-db.sql: %v", err)
if err := z.AddDir("database", dbDump); err != nil {
log.Fatalf("Failed to include database: %v", err)
}
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
@ -133,8 +122,19 @@ func runDump(ctx *cli.Context) error {
}
}
if err := z.AddDir("log", setting.LogRootPath); err != nil {
log.Fatalf("Failed to include log: %v", err)
verPath := filepath.Join(tmpWorkDir, "VERSION")
verf, err := os.Create(verPath)
if err != nil {
log.Fatalf("Failed to create version file: %v", err)
}
_, err = verf.WriteString(setting.AppVer)
verf.Close()
if err != nil {
log.Fatalf("Failed to write version to file: %v", err)
}
if err = z.AddFile("VERSION", verPath); err != nil {
log.Fatalf("Failed to add version file: %v", err)
}
if err = z.Close(); err != nil {

148
cmd/restore.go Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2017 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 cmd
import (
"errors"
"io/ioutil"
"log"
"os"
"path/filepath"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
"github.com/urfave/cli"
)
// CmdRestore represents the available restore sub-command.
var CmdRestore = cli.Command{
Name: "restore",
Usage: "Restore Gitea files and database",
Description: `Restore will restore all data from zip file which dumped from gitea. It will use
the custom config in this dump zip file, this operation will remove all the dest database and repositories.`,
Action: runRestore,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path, if empty will use dumped config file",
},
cli.BoolFlag{
Name: "verbose, v",
Usage: "Show process details",
},
cli.StringFlag{
Name: "tempdir, t",
Value: os.TempDir(),
Usage: "Temporary dir path",
},
},
}
func runRestore(ctx *cli.Context) error {
if len(os.Args) < 3 {
return errors.New("need zip file path")
}
tmpDir := ctx.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
log.Fatalf("Path does not exist: %s", tmpDir)
}
tmpWorkDir, err := ioutil.TempDir(tmpDir, "gitea-dump-")
if err != nil {
log.Fatalf("Failed to create tmp work directory: %v", err)
}
log.Printf("Creating tmp work dir: %s", tmpWorkDir)
// work-around #1103
if os.Getenv("TMPDIR") == "" {
os.Setenv("TMPDIR", tmpWorkDir)
}
srcPath := os.Args[2]
zip.Verbose = ctx.Bool("verbose")
log.Printf("Extracting %s to tmp work dir", srcPath)
err = zip.ExtractTo(srcPath, tmpWorkDir)
if err != nil {
log.Fatalf("Failed to extract %s to tmp work directory: %v", srcPath, err)
}
verData, err := ioutil.ReadFile(filepath.Join(tmpWorkDir, "VERSION"))
if err != nil {
log.Fatalf("Failed to extract %s to tmp work directory: %v", srcPath, err)
}
if setting.AppVer != string(verData) {
log.Fatalf("Expected gitea version to restore is %s, but get %s", string(verData), setting.AppVer)
}
if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config")
} else {
setting.CustomConf = filepath.Join(tmpWorkDir, "custom", "conf", "app.ini")
}
if !com.IsExist(setting.CustomConf) {
log.Fatalf("Failed to load ini config file from %s", setting.CustomConf)
}
setting.NewContext()
//setting.CustomPath = filepath.Join(tmpWorkDir, "custom")
setting.NewXORMLogService(false)
models.LoadConfigs()
err = models.SetEngine()
if err != nil {
log.Fatalf("Failed to SetEngine: %v", err)
}
log.Printf("Restoring repo dir %s ...", setting.RepoRootPath)
repoPath := filepath.Join(tmpWorkDir, "repositories")
err = os.RemoveAll(setting.RepoRootPath)
if err != nil {
log.Fatalf("Failed to Remove repo root path %s: %v", setting.RepoRootPath, err)
}
err = os.Rename(repoPath, setting.RepoRootPath)
if err != nil {
log.Fatalf("Failed to move %s to %s: %v", repoPath, setting.RepoRootPath, err)
}
log.Printf("Restoring custom dir %s ...", setting.CustomPath)
customPath := filepath.Join(tmpWorkDir, "custom")
err = os.RemoveAll(setting.CustomPath)
if err != nil {
log.Fatalf("Failed to Remove repo root path %s: %v", setting.CustomPath, err)
}
err = os.Rename(customPath, setting.CustomPath)
if err != nil {
log.Fatalf("Failed to move %s to %s: %v", customPath, setting.CustomPath, err)
}
log.Printf("Restoring data dir %s ...", setting.AppDataPath)
dataPath := filepath.Join(tmpWorkDir, "data")
err = os.RemoveAll(setting.AppDataPath)
if err != nil {
log.Fatalf("Failed to Remove data root path %s: %v", setting.AppDataPath, err)
}
err = os.Rename(dataPath, setting.AppDataPath)
if err != nil {
log.Fatalf("Failed to move %s to %s: %v", dataPath, setting.AppDataPath, err)
}
log.Printf("Restoring database from ...")
dbPath := filepath.Join(tmpWorkDir, "database")
err = models.RestoreDatabaseFixtures(dbPath)
if err != nil {
log.Fatalf("Failed to restore database dir %s: %v", dbPath, err)
}
return nil
}

View File

@ -43,6 +43,7 @@ arguments - which can alternatively be run by running the subcommand web.`
cmd.CmdServ,
cmd.CmdHook,
cmd.CmdDump,
cmd.CmdRestore,
cmd.CmdCert,
cmd.CmdAdmin,
cmd.CmdGenerate,

View File

@ -8,10 +8,12 @@ import (
"database/sql"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
"reflect"
"strings"
"code.gitea.io/gitea/modules/log"
@ -21,6 +23,7 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"gopkg.in/yaml.v2"
// Needed for the Postgresql driver
_ "github.com/lib/pq"
@ -352,3 +355,80 @@ func DumpDatabase(filePath string, dbType string) error {
}
return x.DumpTablesToFile(tbs, filePath)
}
// DumpDatabaseFixtures dumps all data from database to fixtures files on dirPath
func DumpDatabaseFixtures(dirPath string) error {
for _, t := range tables {
if err := dumpTableFixtures(t, dirPath); err != nil {
return err
}
}
return nil
}
func dumpTableFixtures(bean interface{}, dirPath string) error {
table := x.TableInfo(bean)
f, err := os.Create(filepath.Join(dirPath, table.Name+".yml"))
if err != nil {
return err
}
defer f.Close()
var bufferSize = 100
var objs = make([]interface{}, 0, bufferSize)
err = x.BufferSize(bufferSize).Iterate(bean, func(idx int, obj interface{}) error {
objs = append(objs, obj)
if len(objs) == bufferSize {
// BLOCK: need yaml support gonic name mapper
data, err := yaml.Marshal(objs)
if err != nil {
return err
}
_, err = f.Write(data)
if err != nil {
return err
}
objs = make([]interface{}, 0, bufferSize)
}
return err
})
if err != nil {
return err
}
if len(objs) > 0 {
data, err := yaml.Marshal(objs)
if err != nil {
return err
}
_, err = f.Write(data)
}
return err
}
// RestoreDatabaseFixtures restores all data from dir to database
func RestoreDatabaseFixtures(dirPath string) error {
for _, t := range tables {
if err := restoreTableFixtures(t, dirPath); err != nil {
return err
}
}
return nil
}
func restoreTableFixtures(bean interface{}, dirPath string) error {
table := x.TableInfo(bean)
data, err := ioutil.ReadFile(filepath.Join(dirPath, table.Name+".yml"))
if err != nil {
return err
}
var bufferSize = 100
v := reflect.MakeSlice(table.Type, 0, bufferSize)
// BLOCK: need yaml support gonic name mapper
err = yaml.Unmarshal(data, v.Interface())
if err != nil {
return err
}
_, err = x.Insert(v.Interface())
return err
}