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

1 year ago
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)
}