Get general concept for backup resotration using a unified interface implemented
This commit is contained in:
@@ -20,6 +20,10 @@ const (
|
||||
S3BackupAdapter AdapterType = "s3"
|
||||
)
|
||||
|
||||
// RestoreCallback is a generic restoration callback that exists for both local
|
||||
// and remote backups allowing the files to be restored.
|
||||
type RestoreCallback func(file string, r io.Reader) error
|
||||
|
||||
type ArchiveDetails struct {
|
||||
Checksum string `json:"checksum"`
|
||||
ChecksumType string `json:"checksum_type"`
|
||||
@@ -51,35 +55,33 @@ type Backup struct {
|
||||
|
||||
// noinspection GoNameStartsWithPackageName
|
||||
type BackupInterface interface {
|
||||
// Returns the UUID of this backup as tracked by the panel instance.
|
||||
// Identifier returns the UUID of this backup as tracked by the panel
|
||||
// instance.
|
||||
Identifier() string
|
||||
|
||||
// Attaches additional context to the log output for this backup.
|
||||
// WithLogContext attaches additional context to the log output for this
|
||||
// backup.
|
||||
WithLogContext(map[string]interface{})
|
||||
|
||||
// Generates a backup in whatever the configured source for the specific
|
||||
// implementation is.
|
||||
// Generate creates a backup in whatever the configured source for the
|
||||
// specific implementation is.
|
||||
Generate(string, string) (*ArchiveDetails, error)
|
||||
|
||||
// Returns the ignored files for this backup instance.
|
||||
// Ignored returns the ignored files for this backup instance.
|
||||
Ignored() string
|
||||
|
||||
// Returns a SHA1 checksum for the generated backup.
|
||||
// Checksum returns a SHA1 checksum for the generated backup.
|
||||
Checksum() ([]byte, error)
|
||||
|
||||
// Returns the size of the generated backup.
|
||||
// Size returns the size of the generated backup.
|
||||
Size() (int64, error)
|
||||
|
||||
// Returns the path to the backup on the machine. This is not always the final
|
||||
// storage location of the backup, simply the location we're using to store
|
||||
// it until it is moved to the final spot.
|
||||
// Path returns the path to the backup on the machine. This is not always
|
||||
// the final storage location of the backup, simply the location we're using
|
||||
// to store it until it is moved to the final spot.
|
||||
Path() string
|
||||
|
||||
// Returns details about the archive.
|
||||
// Details returns details about the archive.
|
||||
Details() *ArchiveDetails
|
||||
|
||||
// Removes a backup file.
|
||||
// Remove removes a backup file.
|
||||
Remove() error
|
||||
// Restore is called when a backup is ready to be restored to the disk from
|
||||
// the given source. Not every backup implementation will support this nor
|
||||
// will every implementation require a reader be provided.
|
||||
Restore(reader io.Reader, callback RestoreCallback) error
|
||||
}
|
||||
|
||||
func (b *Backup) Identifier() string {
|
||||
|
||||
@@ -2,7 +2,11 @@ package backup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
"github.com/pterodactyl/wings/system"
|
||||
)
|
||||
|
||||
type LocalBackup struct {
|
||||
@@ -70,3 +74,17 @@ func (b *LocalBackup) Generate(basePath, ignore string) (*ArchiveDetails, error)
|
||||
return b.Details(), nil
|
||||
}
|
||||
|
||||
// Restore will walk over the archive and call the callback function for each
|
||||
// file encountered.
|
||||
func (b *LocalBackup) Restore(_ io.Reader, callback RestoreCallback) error {
|
||||
return archiver.Walk(b.Path(), func(f archiver.File) error {
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
name, err := system.ExtractArchiveSourceName(f, "/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return callback(name, f)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/pterodactyl/wings/api"
|
||||
"github.com/pterodactyl/wings/config"
|
||||
)
|
||||
|
||||
type S3Backup struct {
|
||||
@@ -149,3 +153,40 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore will read from the provided reader assuming that it is a gzipped
|
||||
// tar reader. When a file is encountered in the archive the callback function
|
||||
// will be triggered. If the callback returns an error the entire process is
|
||||
// stopped, otherwise this function will run until all files have been written.
|
||||
//
|
||||
// This restoration uses a workerpool to use up to the number of CPUs available
|
||||
// on the machine when writing files to the disk.
|
||||
func (s *S3Backup) Restore(r io.Reader, callback RestoreCallback) error {
|
||||
reader := r
|
||||
// Steal the logic we use for making backups which will be applied when restoring
|
||||
// this specific backup. This allows us to prevent overloading the disk unintentionally.
|
||||
if writeLimit := int64(config.Get().System.Backups.WriteLimit * 1024 * 1024); writeLimit > 0 {
|
||||
reader = ratelimit.Reader(r, ratelimit.NewBucketWithRate(float64(writeLimit), writeLimit))
|
||||
}
|
||||
gr, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gr.Close()
|
||||
tr := tar.NewReader(gr)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
if header.Typeflag == tar.TypeReg {
|
||||
if err := callback(header.Name, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user