Skip to content

Instantly share code, notes, and snippets.

@xkisu
Created March 24, 2021 23:28
Show Gist options
  • Save xkisu/1ad4cb960d34d52fa81976455c0316d4 to your computer and use it in GitHub Desktop.
Save xkisu/1ad4cb960d34d52fa81976455c0316d4 to your computer and use it in GitHub Desktop.
Restoer Docker Volumes From Folder of Tarfiles
package main
// set GOARCH=amd64
// set GOOS=linux
// go build -o compose-backup ./main.go
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
)
var targetDir string
func init () {
rootCmd.PersistentFlags().StringVarP(&targetDir, "target", "D", "./", "specifies the directory to put backups in and restore from")
rootCmd.AddCommand(restoreCmd)
}
var restoreCmd = &cobra.Command{
Use: "restore",
Short: "Restore backed up volumes from a directory",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("connecting to docker socket")
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
// Pull the default image
fmt.Println("pulling default image")
reader, err := cli.ImagePull(cmd.Context(), "docker.io/library/ubuntu", types.ImagePullOptions{})
if err != nil {
return fmt.Errorf("failed to pull image: %w", err)
}
io.Copy(os.Stdout, reader)
fmt.Println("reading archives from", targetDir)
items, _ := ioutil.ReadDir(targetDir)
for _, item := range items {
if item.IsDir() {
continue
}
// Check the file extension
extension := filepath.Ext(item.Name())
if extension != ".tar" {
continue
}
// Extract the filename as the volume name
filename := filepath.Base(item.Name())
volumeName := filename[0:len(filename)-len(extension)]
fmt.Println("processing", item.Name())
// Attempt to create the volume
fmt.Println("creating volume", volumeName)
vol, err := cli.VolumeCreate(cmd.Context(), volume.VolumeCreateBody{
Driver: "local",
DriverOpts: map[string]string{},
Labels: map[string]string{},
Name: volumeName,
})
if err != nil {
return err
}
// Create the container for unarchiving the backup into the volume
// NOTE: strip-components is required in my case because all the data is in a /data folder in the tar
containerCmd := []string{"tar", "xvf", "/backup/" + filename, "-C", "/volume", "--strip-components=1"}
fmt.Println("creating volume extraction container", strings.Join(containerCmd, " "))
cont, err := cli.ContainerCreate(cmd.Context(), &container.Config{
Image: "ubuntu",
Cmd: containerCmd,
Tty: false,
}, &container.HostConfig{
Mounts: []mount.Mount{
{ // Mount the directory with the backed up files to /backup
Type: mount.TypeBind,
Source: targetDir,
Target: "/backup",
},
},
Binds: []string{
// Mount the target volume to /volume
vol.Name+":/volume",
},
AutoRemove: true,
}, nil, nil, "")
if err != nil {
return fmt.Errorf("failed to create container: %w", err)
}
// Start the container
fmt.Println("starting volume extraction container")
if err := cli.ContainerStart(cmd.Context(), cont.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("failed to start container: %w", err)
}
// Wait for the extraction to finish
fmt.Println("waiting for container extraction to finish")
statusCh, errCh := cli.ContainerWait(cmd.Context(), cont.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
return fmt.Errorf("container exited with error: %w", err)
}
case <-statusCh:
}
}
fmt.Println("finished restoring data to volumes")
return nil
},
}
var rootCmd = &cobra.Command{
Use: "compose-backup",
Short: "Tool to backup and restore docker compose volumes",
}
func main () {
if err := rootCmd.Execute(); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment