Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Vasiliy Ostanin committed May 31, 2019
0 parents commit 20b415b
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
*.iml
.idea
*.json
26 changes: 26 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
hooks:
# you may remove this if you don't use vgo
- go mod download
builds:
- env:
- CGO_ENABLED=0
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
246 changes: 246 additions & 0 deletions cmd/backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package cmd

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"time"

"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"

"github.com/spf13/cobra"

dockertypes "github.com/docker/docker/api/types"
dockerclient "github.com/docker/docker/client"
)

type BackupStruct struct {
Networks map[string]dockertypes.NetworkCreate
Services map[string]swarm.ServiceSpec
Secrets map[string]swarm.SecretSpec
Configs map[string]swarm.ConfigSpec
}

type Evacuation struct {
cli *dockerclient.Client
Backup BackupStruct
}

func Backup(cmd *cobra.Command, args []string) {
dest := args[0]

err := performBackup(dest)
if err != nil {
panic(err)
}
}

func performBackup(dest string) error {
e := Evacuation{
Backup: BackupStruct{},
}

cli, err := dockerclient.NewEnvClient()
if err != nil {
return err
}
e.cli = cli

bg := context.Background()

networks, err := cli.NetworkList(bg, dockertypes.NetworkListOptions{})
if err != nil {
return err
}

e.Backup.Networks = map[string]dockertypes.NetworkCreate{}
networkIdNames := map[string]string{}
skipNetworks := map[string]bool{"bridge": true, "docker_gwbridge": true, "ingress": true, "host": true, "none": true}

for _, network := range networks {
spec := dockertypes.NetworkCreate{
Driver: network.Driver,
EnableIPv6: network.EnableIPv6,
IPAM: &network.IPAM,
Internal: network.Internal,
Attachable: network.Attachable,
Options: network.Options,
Labels: network.Labels,
}

if skipNetworks[network.Name] {
continue
}

networkIdNames[network.ID] = network.Name
e.Backup.Networks[network.Name] = spec
}

// services
services, err := cli.ServiceList(bg, dockertypes.ServiceListOptions{})
if err != nil {
return err
}
e.Backup.Services = map[string]swarm.ServiceSpec{}

services, err = cli.ServiceList(bg, dockertypes.ServiceListOptions{})
if err != nil {
return err
}
e.Backup.Services = map[string]swarm.ServiceSpec{}

for _, service := range services {
if service.Spec.Name == "evacuation" {
continue
}

serviceSpec := service.Spec
for i, n := range serviceSpec.Networks {
serviceSpec.Networks[i].Target = networkIdNames[n.Target]
}

for i, n := range serviceSpec.TaskTemplate.Networks {
serviceSpec.TaskTemplate.Networks[i].Target = networkIdNames[n.Target]
}

for i, _ := range serviceSpec.TaskTemplate.ContainerSpec.Secrets {
serviceSpec.TaskTemplate.ContainerSpec.Secrets[i].SecretID = ""
}

for i, _ := range serviceSpec.TaskTemplate.ContainerSpec.Configs {
serviceSpec.TaskTemplate.ContainerSpec.Configs[i].ConfigID = ""
}

e.Backup.Services[serviceSpec.Name] = serviceSpec
}

err = e.LoadSecretsData()
if err != nil {
return err
}

bjson, err := json.MarshalIndent(e.Backup, "", " ")
if err != nil {
return err
}

return ioutil.WriteFile(dest, bjson, os.FileMode(0600))
}

func (e *Evacuation) LoadSecretsData() error {
bg := context.Background()
secretReferences := []*swarm.SecretReference{}

info, err := e.cli.Info(bg)
if err != nil {
return err
}

// secrets
e.Backup.Secrets = map[string]swarm.SecretSpec{}
secrets, err := e.cli.SecretList(bg, dockertypes.SecretListOptions{})
for _, secret := range secrets {
e.Backup.Secrets[secret.Spec.Name] = secret.Spec
secretReferences = append(secretReferences, &swarm.SecretReference{
SecretName: secret.Spec.Name,
SecretID: secret.ID,
File: &swarm.SecretReferenceFileTarget{
Name: secret.Spec.Name,
UID: "0",
GID: "0",
Mode: os.FileMode(020),
},
})
}

// configs
e.Backup.Configs = map[string]swarm.ConfigSpec{}
configs, err := e.cli.ConfigList(bg, dockertypes.ConfigListOptions{})
for _, config := range configs {
e.Backup.Configs[config.Spec.Name] = config.Spec
}

loaderSpec := swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
Placement: &swarm.Placement{
Constraints: []string{fmt.Sprintf("node.id==%s", info.Swarm.NodeID)},
},
ContainerSpec: &swarm.ContainerSpec{
Image: "busybox",
Command: []string{"sleep", "100000"},
Secrets: secretReferences,
},
},
}
loaderSpec.Name = "evacuation"

_ = e.cli.ServiceRemove(bg, "evacuation")

service, err := e.cli.ServiceCreate(bg, loaderSpec, dockertypes.ServiceCreateOptions{})
if err != nil {
return err
}

// "com.docker.swarm.service.name"
containerFilter := filters.NewArgs()
containerFilter.Add("label", fmt.Sprintf("com.docker.swarm.service.id=%s", service.ID))

var container *dockertypes.Container
tries := 0

for {
if tries > 10 {
return errors.New("failed to create export container")
}
containers, _ := e.cli.ContainerList(bg, dockertypes.ContainerListOptions{Filters: containerFilter})
if len(containers) > 0 {
container = &containers[0]
break
}

tries += 1
time.Sleep(time.Second * 5)
}

fmt.Printf("evac container id: %s\n", container.ID)
for i, s := range e.Backup.Secrets {
fmt.Printf("loading %v\n", fmt.Sprintf("/run/secrets/%s", s.Name))

id, err := e.cli.ContainerExecCreate(bg, container.ID, dockertypes.ExecConfig{
AttachStdout: true,
Detach: false,
Tty: true,
Cmd: []string{"cat", fmt.Sprintf("/run/secrets/%s", s.Name)},
})

if err != nil {
return err
}

containerConn, err := e.cli.ContainerExecAttach(bg, id.ID, dockertypes.ExecStartCheck{Detach: false, Tty: true})
if err != nil {
return err
}

defer containerConn.Close()

buf := new(bytes.Buffer)

_, err = containerConn.Reader.WriteTo(buf)
if err != nil {
return err
}

containerConn.Close()

s.Data = buf.Bytes()
e.Backup.Secrets[i] = s
}

return nil
}
85 changes: 85 additions & 0 deletions cmd/restore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"context"
"encoding/json"
"io/ioutil"

"github.com/docker/docker/api/types"
"github.com/spf13/cobra"

dockerclient "github.com/docker/docker/client"
)

func Restore(cmd *cobra.Command, args []string) {
src := args[0]

err := performRestore(src)
if err != nil {
panic(err)
}
}

func performRestore(src string) error {
b, err := ioutil.ReadFile(src)
if err != nil {
return err
}

e := Evacuation{}
cli, err := dockerclient.NewEnvClient()
if err != nil {
return err
}
e.cli = cli

bg := context.Background()

err = json.Unmarshal(b, &e.Backup)
if err != nil {
return err
}

for name, net := range e.Backup.Networks {
_, err := e.cli.NetworkCreate(bg, name, net)
if err != nil {
return err
}
}

secretNameId := map[string]string{}
for n, s := range e.Backup.Secrets {
r, err := e.cli.SecretCreate(bg, s)
if err != nil {
return err
}

secretNameId[n] = r.ID
}

configNameId := map[string]string{}
for n, c := range e.Backup.Configs {
r, err := e.cli.ConfigCreate(bg, c)
if err != nil {
return err
}

configNameId[n] = r.ID
}

for _, service := range e.Backup.Services {
for is, secret := range service.TaskTemplate.ContainerSpec.Secrets {
service.TaskTemplate.ContainerSpec.Secrets[is].SecretID = secretNameId[secret.SecretName]
}
for ic, config := range service.TaskTemplate.ContainerSpec.Configs {
service.TaskTemplate.ContainerSpec.Configs[ic].ConfigID = configNameId[config.ConfigName]
}

_, err = e.cli.ServiceCreate(bg, service, types.ServiceCreateOptions{})
if err != nil {
return err
}
}

return nil
}
18 changes: 18 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module swarm-backup

require (
github.com/Microsoft/go-winio v0.4.12 // indirect
github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gogo/protobuf v1.2.1 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.4
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
)

replace github.com/docker/docker v1.13.1 => github.com/docker/engine v0.0.0-20180718150940-a3ef7e9a9bda
Loading

0 comments on commit 20b415b

Please sign in to comment.