forked from Nixius/authelia
132 lines
3.6 KiB
Go
132 lines
3.6 KiB
Go
package swarm
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ArchiveVolumes tars each Docker volume belonging to stackName into archiveDir
|
|
// and removes the volumes afterwards. Archive layout:
|
|
//
|
|
// {archiveDir}/{stackName}/{volumeName}.tar.gz
|
|
func (c *Client) ArchiveVolumes(stackName, archiveDir string) error {
|
|
vols, err := c.listVolumes(stackName)
|
|
if err != nil {
|
|
return fmt.Errorf("list volumes: %w", err)
|
|
}
|
|
if len(vols) == 0 {
|
|
log.Printf("archive: no volumes found for %s, nothing to archive", stackName)
|
|
return nil
|
|
}
|
|
|
|
dir := filepath.Join(archiveDir, stackName)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("create archive dir: %w", err)
|
|
}
|
|
|
|
for _, vol := range vols {
|
|
tarName := vol + ".tar.gz"
|
|
log.Printf("archive: backing up volume %s -> %s/%s", vol, dir, tarName)
|
|
|
|
cmd := exec.Command("docker", "run", "--rm",
|
|
"-v", vol+":/data:ro",
|
|
"-v", dir+":/backup",
|
|
"alpine",
|
|
"tar", "czf", "/backup/"+tarName, "-C", "/data", ".",
|
|
)
|
|
cmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost)
|
|
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
log.Printf("archive: failed to tar %s: %s: %v", vol, strings.TrimSpace(string(out)), err)
|
|
continue
|
|
}
|
|
|
|
rmCmd := exec.Command("docker", "volume", "rm", vol)
|
|
rmCmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost)
|
|
if out, err := rmCmd.CombinedOutput(); err != nil {
|
|
log.Printf("archive: failed to remove volume %s: %s: %v", vol, strings.TrimSpace(string(out)), err)
|
|
}
|
|
}
|
|
|
|
log.Printf("archive: completed for %s (%d volumes)", stackName, len(vols))
|
|
return nil
|
|
}
|
|
|
|
// RestoreVolumes recreates Docker volumes from a previous archive. No-op if
|
|
// no archive directory exists for the stack (fresh customer).
|
|
func (c *Client) RestoreVolumes(stackName, archiveDir string) error {
|
|
dir := filepath.Join(archiveDir, stackName)
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("read archive dir: %w", err)
|
|
}
|
|
|
|
restored := 0
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
if !strings.HasSuffix(name, ".tar.gz") {
|
|
continue
|
|
}
|
|
vol := strings.TrimSuffix(name, ".tar.gz")
|
|
|
|
log.Printf("restore: extracting %s -> volume %s", name, vol)
|
|
|
|
createCmd := exec.Command("docker", "volume", "create", vol)
|
|
createCmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost)
|
|
if out, err := createCmd.CombinedOutput(); err != nil {
|
|
log.Printf("restore: volume create %s failed: %s: %v", vol, strings.TrimSpace(string(out)), err)
|
|
continue
|
|
}
|
|
|
|
cmd := exec.Command("docker", "run", "--rm",
|
|
"-v", vol+":/data",
|
|
"-v", dir+":/backup:ro",
|
|
"alpine",
|
|
"tar", "xzf", "/backup/"+name, "-C", "/data",
|
|
)
|
|
cmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost)
|
|
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
log.Printf("restore: extract %s failed: %s: %v", name, strings.TrimSpace(string(out)), err)
|
|
continue
|
|
}
|
|
|
|
os.Remove(filepath.Join(dir, name))
|
|
restored++
|
|
}
|
|
|
|
if restored > 0 {
|
|
os.Remove(dir)
|
|
log.Printf("restore: completed for %s (%d volumes)", stackName, restored)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) listVolumes(stackName string) ([]string, error) {
|
|
cmd := exec.Command("docker", "volume", "ls",
|
|
"--filter", "name="+stackName+"_",
|
|
"--format", "{{.Name}}",
|
|
)
|
|
cmd.Env = append(os.Environ(), "DOCKER_HOST="+c.cfg.DockerHost)
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("docker volume ls: %s: %w", strings.TrimSpace(string(out)), err)
|
|
}
|
|
|
|
var vols []string
|
|
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
|
if line != "" {
|
|
vols = append(vols, line)
|
|
}
|
|
}
|
|
return vols, nil
|
|
}
|