Implement initial 'wdcli backup_instance' command

This commit performs an initial implementation of the 'backup_instance'
command.
This commit is contained in:
Tom Wiesing 2022-09-05 14:43:50 +02:00
parent a64c02cd78
commit 2a14d93d3c
No known key found for this signature in database
17 changed files with 437 additions and 135 deletions

52
env/backup.go vendored Normal file
View file

@ -0,0 +1,52 @@
package env
import (
"fmt"
"os"
"path/filepath"
"sync/atomic"
"time"
)
func (dis Distillery) BackupDir() string {
return filepath.Join(dis.Config.DeployRoot, "backups")
}
func (dis Distillery) InprogressBackupPath() string {
return filepath.Join(dis.BackupDir(), "inprogress")
}
func (dis Distillery) FinalBackupPath() string {
return filepath.Join(dis.BackupDir(), "final")
}
// NewFinalBackupFile returns the path to a new final backup file.
func (dis Distillery) FinalBackupArchive(prefix string) string {
counter := atomic.AddUint64(&globalBackupCounter, 1)
// generate a new name with the current time, a global counter, and the prefix
name := fmt.Sprintf("%s-%d-%d.tar.gz", prefix, time.Now().Unix(), counter)
path := filepath.Join(dis.FinalBackupPath(), name)
return path
}
var globalBackupCounter uint64
// NewInprogressBackupPath returns the path to a new inprogress backup directory.
// The directory is guaranteed to have been freshly created.
func (dis Distillery) NewInprogressBackupPath(prefix string) (string, error) {
counter := atomic.AddUint64(&globalBackupCounter, 1)
// generate a new name with the current time, a global counter, and the prefix
name := fmt.Sprintf("%s-%d-%d", prefix, time.Now().Unix(), counter)
path := filepath.Join(dis.InprogressBackupPath(), name)
// create the directory
if err := os.Mkdir(path, os.ModeDir); err != nil {
return "", err
}
// and it is here!
return path, nil
}

23
env/dirs.go vendored
View file

@ -1,23 +0,0 @@
package env
import "path/filepath"
func (dis Distillery) BackupDir() string {
return filepath.Join(dis.Config.DeployRoot, "backups")
}
func (dis Distillery) RuntimeDir() string {
return filepath.Join(dis.Config.DeployRoot, "runtime")
}
func (dis Distillery) RuntimeUtilsDir() string {
return filepath.Join(dis.Config.DeployRoot, "runtime", "utils")
}
func (dis Distillery) InprogressBackupPath() string {
return filepath.Join(dis.BackupDir(), "inprogress")
}
func (dis Distillery) FinalBackupPath() string {
return filepath.Join(dis.BackupDir(), "final")
}

5
env/instances.go vendored
View file

@ -30,6 +30,11 @@ var ErrInstanceNotFound = exit.Error{
ExitCode: exit.ExitGeneric,
}
var errSQL = exit.Error{
Message: "Unknown SQL Error %s",
ExitCode: exit.ExitGeneric,
}
// Instance returns the instance of the WissKI Distillery with the provided slug
func (dis *Distillery) Instance(slug string) (i Instance, err error) {
if err := dis.SQLWaitForConnection(); err != nil {

13
env/runtime.go vendored Normal file
View file

@ -0,0 +1,13 @@
package env
import "path/filepath"
// RuntimeDir returns the path to the runtime directory
func (dis Distillery) RuntimeDir() string {
return filepath.Join(dis.Config.DeployRoot, "runtime")
}
// RuntimeUtilsDir returns the path to the runtime utility dir
func (dis Distillery) RuntimeUtilsDir() string {
return filepath.Join(dis.Config.DeployRoot, "runtime", "utils")
}

2
env/stack.go vendored
View file

@ -6,6 +6,8 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/stack"
)
// TODO: Move everything into specific subpackages
// Stacks returns the Stacks of this distillery
func (dis *Distillery) Stacks() []stack.Installable {
// TODO: Do we want to cache these stacks?

17
env/stack_sql.go vendored
View file

@ -2,6 +2,7 @@ package env
import (
"fmt"
"io"
"io/fs"
"time"
@ -76,6 +77,22 @@ func (dis *Distillery) sqlBkTable(silent bool) (*gorm.DB, error) {
return table, nil
}
var errSQLBackup = errors.New("SQLBackup: Mysqldump returned non-zero exit code")
// SQLBackup makes a backup of the sql database into dest.
func (dis *Distillery) SQLBackup(io stream.IOStream, dest io.Writer, database string) error {
io = stream.NewIOStream(dest, io.Stderr, nil, 0)
code, err := dis.SQLStack().Exec(io, "sql", "mysqldump", "--database", database)
if err != nil {
return err
}
if code != 0 {
return errSQLBackup
}
return nil
}
// SQLShell executes a mysql shell inside the SQLStack.
func (dis *Distillery) SQLShell(io stream.IOStream, argv ...string) (int, error) {
return dis.SQLStack().Exec(io, "sql", "mysql", argv...)

View file

@ -200,6 +200,21 @@ func (dis *Distillery) TriplestorePurgeRepo(repo string) error {
return nil
}
var errTSBackupWrongStatusCode = errors.New("Distillery.Backup: Wrong status code")
// TriplestoreBackup backs up the repository named repo into the writer dst.
func (dis *Distillery) TriplestoreBackup(dst io.Writer, repo string) (int64, error) {
res, err := dis.triplestoreRequest("GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads")
if err != nil {
return 0, err
}
if res.StatusCode != http.StatusOK {
return 0, errTSBackupWrongStatusCode
}
defer res.Body.Close()
return io.Copy(dst, res.Body)
}
var errTriplestoreFailedSecurity = errors.New("failed to enable triplestore security: request did not succeed with HTTP 200 OK")
func (dis *Distillery) TriplestoreBootstrap(io stream.IOStream) error {