You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
4.5 KiB
Go
237 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
optStart = false
|
|
|
|
restoreCmd = &cobra.Command{
|
|
Use: "restore <backup file>",
|
|
Short: "restores a backup of a container",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) < 1 {
|
|
return fmt.Errorf("restore requires a .json or .tar backup")
|
|
}
|
|
|
|
if strings.HasSuffix(args[0], ".json") {
|
|
return restore(args[0])
|
|
} else if strings.HasSuffix(args[0], ".tar") {
|
|
return restoreTar(args[0])
|
|
}
|
|
|
|
return fmt.Errorf("Unknown file type, please provide a .tar or .json file")
|
|
},
|
|
}
|
|
)
|
|
|
|
func restoreTar(filename string) error {
|
|
tarfile, err := os.Open(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tarfile.Close()
|
|
|
|
tr := tar.NewReader(tarfile)
|
|
var b []byte
|
|
for {
|
|
th, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch th.Name {
|
|
case "container.json":
|
|
var err error
|
|
b, err = ioutil.ReadAll(tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
var backup Backup
|
|
err = json.Unmarshal(b, &backup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id, err := createContainer(backup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conf, err := cli.ContainerInspect(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tt := map[string]string{}
|
|
for _, oldPath := range backup.Mounts {
|
|
for _, hostPath := range conf.Mounts {
|
|
if oldPath.Destination == hostPath.Destination {
|
|
tt[oldPath.Source] = hostPath.Source
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if _, err := tarfile.Seek(0, 0); err != nil {
|
|
return err
|
|
}
|
|
tr = tar.NewReader(tarfile)
|
|
for {
|
|
th, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if th.Name == "container.json" {
|
|
continue
|
|
}
|
|
|
|
path := th.Name
|
|
fmt.Println("Restoring:", path)
|
|
for k, v := range tt {
|
|
if strings.HasPrefix(path, k) {
|
|
path = v + path[len(k):]
|
|
}
|
|
}
|
|
|
|
if th.Typeflag == tar.TypeDir {
|
|
if err := os.MkdirAll(path, os.FileMode(th.Mode)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.Copy(file, tr); err != nil {
|
|
return err
|
|
}
|
|
file.Close()
|
|
}
|
|
if err := os.Chmod(path, os.FileMode(th.Mode)); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Chown(path, th.Uid, th.Gid); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("Created as:", path)
|
|
}
|
|
|
|
if optStart {
|
|
return startContainer(id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func restore(filename string) error {
|
|
var backup Backup
|
|
b, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = json.Unmarshal(b, &backup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id, err := createContainer(backup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if optStart {
|
|
return startContainer(id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createContainer(backup Backup) (string, error) {
|
|
nameparts := strings.Split(backup.Name, "/")
|
|
name := nameparts[len(nameparts)-1]
|
|
fmt.Println("Restoring Container:", name)
|
|
|
|
_, _, err := cli.ImageInspectWithRaw(ctx, backup.Config.Image)
|
|
if err != nil {
|
|
fmt.Println("Pulling Image:", backup.Config.Image)
|
|
_, err := cli.ImagePull(ctx, backup.Config.Image, types.ImagePullOptions{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
// io.Copy(os.Stdout, reader)
|
|
|
|
resp, err := cli.ContainerCreate(ctx, backup.Config, &container.HostConfig{
|
|
PortBindings: backup.PortMap,
|
|
}, nil, name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fmt.Println("Created Container with ID:", resp.ID)
|
|
|
|
for _, m := range backup.Mounts {
|
|
fmt.Printf("Old Mount (type %s) %s -> %s\n", m.Type, m.Source, m.Destination)
|
|
}
|
|
|
|
conf, err := cli.ContainerInspect(ctx, resp.ID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, m := range conf.Mounts {
|
|
fmt.Printf("New Mount (type %s) %s -> %s\n", m.Type, m.Source, m.Destination)
|
|
}
|
|
|
|
return resp.ID, nil
|
|
}
|
|
|
|
func startContainer(id string) error {
|
|
fmt.Println("Starting container:", id[:12])
|
|
|
|
err := cli.ContainerStart(ctx, id, types.ContainerStartOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
/*
|
|
statusCh, errCh := cli.ContainerWait(ctx, id, container.WaitConditionNotRunning)
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case <-statusCh:
|
|
}
|
|
|
|
out, err := cli.ContainerLogs(ctx, id, types.ContainerLogsOptions{ShowStdout: true})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
io.Copy(os.Stdout, out)
|
|
*/
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
restoreCmd.Flags().BoolVarP(&optStart, "start", "s", false, "start restored container")
|
|
RootCmd.AddCommand(restoreCmd)
|
|
}
|