Skip to content

Commit 684b7a9

Browse files
PhilippHomannzeripathtechknowlogick
authored
Dump: add output format tar and output to stdout (#10376)
* Dump: Use mholt/archive/v3 to support tar including many compressions Signed-off-by: Philipp Homann <[email protected]> * Dump: Allow dump output to stdout Signed-off-by: Philipp Homann <[email protected]> * Dump: Fixed bug present since #6677 where SessionConfig.Provider is never "file" Signed-off-by: Philipp Homann <[email protected]> * Dump: never pack RepoRootPath, LFS.ContentPath and LogRootPath when they are below AppDataPath Signed-off-by: Philipp Homann <[email protected]> * Dump: also dump LFS (fixes #10058) Signed-off-by: Philipp Homann <[email protected]> * Dump: never dump CustomPath if CustomPath is a subdir of or equal to AppDataPath (fixes #10365) Signed-off-by: Philipp Homann <[email protected]> * Use log.Info instead of fmt.Fprintf Signed-off-by: Philipp Homann <[email protected]> * import ordering * make fmt Co-authored-by: zeripath <[email protected]> Co-authored-by: techknowlogick <[email protected]> Co-authored-by: Matti R <[email protected]>
1 parent 209b17c commit 684b7a9

File tree

303 files changed

+301315
-1181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

303 files changed

+301315
-1181
lines changed

cmd/dump.go

Lines changed: 212 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,120 @@
66
package cmd
77

88
import (
9+
"encoding/json"
910
"fmt"
1011
"io/ioutil"
1112
"os"
1213
"path"
1314
"path/filepath"
15+
"strings"
1416
"time"
1517

1618
"code.gitea.io/gitea/models"
1719
"code.gitea.io/gitea/modules/log"
1820
"code.gitea.io/gitea/modules/setting"
1921

20-
"github.com/unknwon/cae/zip"
22+
"gitea.com/macaron/session"
23+
archiver "github.com/mholt/archiver/v3"
2124
"github.com/unknwon/com"
2225
"github.com/urfave/cli"
2326
)
2427

28+
func addFile(w archiver.Writer, filePath string, absPath string, verbose bool) error {
29+
if verbose {
30+
log.Info("Adding file %s\n", filePath)
31+
}
32+
file, err := os.Open(absPath)
33+
if err != nil {
34+
return err
35+
}
36+
defer file.Close()
37+
fileInfo, err := file.Stat()
38+
if err != nil {
39+
return err
40+
}
41+
42+
return w.Write(archiver.File{
43+
FileInfo: archiver.FileInfo{
44+
FileInfo: fileInfo,
45+
CustomName: filePath,
46+
},
47+
ReadCloser: file,
48+
})
49+
}
50+
51+
func addRecursive(w archiver.Writer, dirPath string, absPath string, verbose bool) error {
52+
if verbose {
53+
log.Info("Adding dir %s\n", dirPath)
54+
}
55+
dir, err := os.Open(absPath)
56+
if err != nil {
57+
return fmt.Errorf("Could not open directory %s: %s", absPath, err)
58+
}
59+
files, err := dir.Readdir(0)
60+
if err != nil {
61+
return fmt.Errorf("Unable to list files in %s: %s", absPath, err)
62+
}
63+
64+
if err := addFile(w, dirPath, absPath, false); err != nil {
65+
return err
66+
}
67+
68+
for _, fileInfo := range files {
69+
if fileInfo.IsDir() {
70+
err = addRecursive(w, filepath.Join(dirPath, fileInfo.Name()), filepath.Join(absPath, fileInfo.Name()), verbose)
71+
} else {
72+
err = addFile(w, filepath.Join(dirPath, fileInfo.Name()), filepath.Join(absPath, fileInfo.Name()), verbose)
73+
}
74+
if err != nil {
75+
return err
76+
}
77+
}
78+
return nil
79+
}
80+
81+
func isSubdir(upper string, lower string) (bool, error) {
82+
if relPath, err := filepath.Rel(upper, lower); err != nil {
83+
return false, err
84+
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
85+
return true, nil
86+
}
87+
return false, nil
88+
}
89+
90+
type outputType struct {
91+
Enum []string
92+
Default string
93+
selected string
94+
}
95+
96+
func (o outputType) Join() string {
97+
return strings.Join(o.Enum, ", ")
98+
}
99+
100+
func (o *outputType) Set(value string) error {
101+
for _, enum := range o.Enum {
102+
if enum == value {
103+
o.selected = value
104+
return nil
105+
}
106+
}
107+
108+
return fmt.Errorf("allowed values are %s", o.Join())
109+
}
110+
111+
func (o outputType) String() string {
112+
if o.selected == "" {
113+
return o.Default
114+
}
115+
return o.selected
116+
}
117+
118+
var outputTypeEnum = &outputType{
119+
Enum: []string{"zip", "tar", "tar.gz", "tar.xz", "tar.bz2"},
120+
Default: "zip",
121+
}
122+
25123
// CmdDump represents the available dump sub-command.
26124
var CmdDump = cli.Command{
27125
Name: "dump",
@@ -33,7 +131,7 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
33131
cli.StringFlag{
34132
Name: "file, f",
35133
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
36-
Usage: "Name of the dump file which will be created.",
134+
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
37135
},
38136
cli.BoolFlag{
39137
Name: "verbose, V",
@@ -56,6 +154,11 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
56154
Name: "skip-log, L",
57155
Usage: "Skip the log dumping",
58156
},
157+
cli.GenericFlag{
158+
Name: "type",
159+
Value: outputTypeEnum,
160+
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
161+
},
59162
},
60163
}
61164

@@ -65,79 +168,113 @@ func fatal(format string, args ...interface{}) {
65168
}
66169

67170
func runDump(ctx *cli.Context) error {
171+
var file *os.File
172+
fileName := ctx.String("file")
173+
if fileName == "-" {
174+
file = os.Stdout
175+
err := log.DelLogger("console")
176+
if err != nil {
177+
fatal("Deleting default logger failed. Can not write to stdout: %v", err)
178+
}
179+
}
68180
setting.NewContext()
181+
// make sure we are logging to the console no matter what the configuration tells us do to
182+
if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
183+
fatal("Setting logging mode to console failed: %v", err)
184+
}
185+
if _, err := setting.Cfg.Section("log.console").NewKey("STDERR", "true"); err != nil {
186+
fatal("Setting console logger to stderr failed: %v", err)
187+
}
69188
setting.NewServices() // cannot access session settings otherwise
70189

71190
err := models.SetEngine()
72191
if err != nil {
73192
return err
74193
}
75194

76-
tmpDir := ctx.String("tempdir")
77-
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
78-
fatal("Path does not exist: %s", tmpDir)
79-
}
80-
tmpWorkDir, err := ioutil.TempDir(tmpDir, "gitea-dump-")
81-
if err != nil {
82-
fatal("Failed to create tmp work directory: %v", err)
195+
if file == nil {
196+
file, err = os.Create(fileName)
197+
if err != nil {
198+
fatal("Unable to open %s: %v", fileName, err)
199+
}
83200
}
84-
log.Info("Creating tmp work dir: %s", tmpWorkDir)
201+
defer file.Close()
85202

86-
// work-around #1103
87-
if os.Getenv("TMPDIR") == "" {
88-
os.Setenv("TMPDIR", tmpWorkDir)
203+
verbose := ctx.Bool("verbose")
204+
outType := ctx.String("type")
205+
var iface interface{}
206+
if fileName == "-" {
207+
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
208+
} else {
209+
iface, err = archiver.ByExtension(fileName)
89210
}
90-
91-
dbDump := path.Join(tmpWorkDir, "gitea-db.sql")
92-
93-
fileName := ctx.String("file")
94-
log.Info("Packing dump files...")
95-
z, err := zip.Create(fileName)
96211
if err != nil {
97-
fatal("Failed to create %s: %v", fileName, err)
212+
fatal("Unable to get archiver for extension: %v", err)
98213
}
99214

100-
zip.Verbose = ctx.Bool("verbose")
215+
w, _ := iface.(archiver.Writer)
216+
if err := w.Create(file); err != nil {
217+
fatal("Creating archiver.Writer failed: %v", err)
218+
}
219+
defer w.Close()
101220

102221
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
103222
log.Info("Skip dumping local repositories")
104223
} else {
105-
log.Info("Dumping local repositories...%s", setting.RepoRootPath)
106-
reposDump := path.Join(tmpWorkDir, "gitea-repo.zip")
107-
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
108-
fatal("Failed to dump local repositories: %v", err)
224+
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
225+
if err := addRecursive(w, "repos", setting.RepoRootPath, verbose); err != nil {
226+
fatal("Failed to include repositories: %v", err)
109227
}
110-
if err := z.AddFile("gitea-repo.zip", reposDump); err != nil {
111-
fatal("Failed to include gitea-repo.zip: %v", err)
228+
229+
if _, err := os.Stat(setting.LFS.ContentPath); !os.IsNotExist(err) {
230+
log.Info("Dumping lfs... %s", setting.LFS.ContentPath)
231+
if err := addRecursive(w, "lfs", setting.LFS.ContentPath, verbose); err != nil {
232+
fatal("Failed to include lfs: %v", err)
233+
}
112234
}
113235
}
114236

237+
tmpDir := ctx.String("tempdir")
238+
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
239+
fatal("Path does not exist: %s", tmpDir)
240+
}
241+
242+
dbDump, err := ioutil.TempFile(tmpDir, "gitea-db.sql")
243+
if err != nil {
244+
fatal("Failed to create tmp file: %v", err)
245+
}
246+
defer os.Remove(dbDump.Name())
247+
115248
targetDBType := ctx.String("database")
116249
if len(targetDBType) > 0 && targetDBType != setting.Database.Type {
117250
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
118251
} else {
119252
log.Info("Dumping database...")
120253
}
121254

122-
if err := models.DumpDatabase(dbDump, targetDBType); err != nil {
255+
if err := models.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
123256
fatal("Failed to dump database: %v", err)
124257
}
125258

126-
if err := z.AddFile("gitea-db.sql", dbDump); err != nil {
259+
if err := addFile(w, "gitea-db.sql", dbDump.Name(), verbose); err != nil {
127260
fatal("Failed to include gitea-db.sql: %v", err)
128261
}
129262

130263
if len(setting.CustomConf) > 0 {
131264
log.Info("Adding custom configuration file from %s", setting.CustomConf)
132-
if err := z.AddFile("app.ini", setting.CustomConf); err != nil {
265+
if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
133266
fatal("Failed to include specified app.ini: %v", err)
134267
}
135268
}
136269

137270
customDir, err := os.Stat(setting.CustomPath)
138271
if err == nil && customDir.IsDir() {
139-
if err := z.AddDir("custom", setting.CustomPath); err != nil {
140-
fatal("Failed to include custom: %v", err)
272+
if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
273+
if err := addRecursive(w, "custom", setting.CustomPath, verbose); err != nil {
274+
fatal("Failed to include custom: %v", err)
275+
}
276+
} else {
277+
log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
141278
}
142279
} else {
143280
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
@@ -146,11 +283,19 @@ func runDump(ctx *cli.Context) error {
146283
if com.IsExist(setting.AppDataPath) {
147284
log.Info("Packing data directory...%s", setting.AppDataPath)
148285

149-
var sessionAbsPath string
150-
if setting.SessionConfig.Provider == "file" {
151-
sessionAbsPath = setting.SessionConfig.ProviderConfig
286+
var excludes []string
287+
if setting.Cfg.Section("session").Key("PROVIDER").Value() == "file" {
288+
var opts session.Options
289+
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
290+
return err
291+
}
292+
excludes = append(excludes, opts.ProviderConfig)
152293
}
153-
if err := zipAddDirectoryExclude(z, "data", setting.AppDataPath, sessionAbsPath); err != nil {
294+
295+
excludes = append(excludes, setting.RepoRootPath)
296+
excludes = append(excludes, setting.LFS.ContentPath)
297+
excludes = append(excludes, setting.LogRootPath)
298+
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
154299
fatal("Failed to include data directory: %v", err)
155300
}
156301
}
@@ -161,32 +306,42 @@ func runDump(ctx *cli.Context) error {
161306
if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
162307
log.Info("Skip dumping log files")
163308
} else if com.IsExist(setting.LogRootPath) {
164-
if err := z.AddDir("log", setting.LogRootPath); err != nil {
309+
if err := addRecursive(w, "log", setting.LogRootPath, verbose); err != nil {
165310
fatal("Failed to include log: %v", err)
166311
}
167312
}
168313

169-
if err = z.Close(); err != nil {
170-
_ = os.Remove(fileName)
171-
fatal("Failed to save %s: %v", fileName, err)
172-
}
314+
if fileName != "-" {
315+
if err = w.Close(); err != nil {
316+
_ = os.Remove(fileName)
317+
fatal("Failed to save %s: %v", fileName, err)
318+
}
173319

174-
if err := os.Chmod(fileName, 0600); err != nil {
175-
log.Info("Can't change file access permissions mask to 0600: %v", err)
320+
if err := os.Chmod(fileName, 0600); err != nil {
321+
log.Info("Can't change file access permissions mask to 0600: %v", err)
322+
}
176323
}
177324

178-
log.Info("Removing tmp work dir: %s", tmpWorkDir)
179-
180-
if err := os.RemoveAll(tmpWorkDir); err != nil {
181-
fatal("Failed to remove %s: %v", tmpWorkDir, err)
325+
if fileName != "-" {
326+
log.Info("Finish dumping in file %s", fileName)
327+
} else {
328+
log.Info("Finish dumping to stdout")
182329
}
183-
log.Info("Finish dumping in file %s", fileName)
184330

185331
return nil
186332
}
187333

188-
// zipAddDirectoryExclude zips absPath to specified zipPath inside z excluding excludeAbsPath
189-
func zipAddDirectoryExclude(zip *zip.ZipArchive, zipPath, absPath string, excludeAbsPath string) error {
334+
func contains(slice []string, s string) bool {
335+
for _, v := range slice {
336+
if v == s {
337+
return true
338+
}
339+
}
340+
return false
341+
}
342+
343+
// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
344+
func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
190345
absPath, err := filepath.Abs(absPath)
191346
if err != nil {
192347
return err
@@ -197,24 +352,24 @@ func zipAddDirectoryExclude(zip *zip.ZipArchive, zipPath, absPath string, exclud
197352
}
198353
defer dir.Close()
199354

200-
zip.AddEmptyDir(zipPath)
201-
202355
files, err := dir.Readdir(0)
203356
if err != nil {
204357
return err
205358
}
206359
for _, file := range files {
207360
currentAbsPath := path.Join(absPath, file.Name())
208-
currentZipPath := path.Join(zipPath, file.Name())
361+
currentInsidePath := path.Join(insidePath, file.Name())
209362
if file.IsDir() {
210-
if currentAbsPath != excludeAbsPath {
211-
if err = zipAddDirectoryExclude(zip, currentZipPath, currentAbsPath, excludeAbsPath); err != nil {
363+
if !contains(excludeAbsPath, currentAbsPath) {
364+
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
365+
return err
366+
}
367+
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
212368
return err
213369
}
214370
}
215-
216371
} else {
217-
if err = zip.AddFile(currentZipPath, currentAbsPath); err != nil {
372+
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
218373
return err
219374
}
220375
}

0 commit comments

Comments
 (0)