Skip to content

Commit 20b415b

Browse files
author
Vasiliy Ostanin
committed
init
0 parents  commit 20b415b

File tree

8 files changed

+498
-0
lines changed

8 files changed

+498
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist/
2+
*.iml
3+
.idea
4+
*.json

.goreleaser.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# This is an example goreleaser.yaml file with some sane defaults.
2+
# Make sure to check the documentation at http://goreleaser.com
3+
before:
4+
hooks:
5+
# you may remove this if you don't use vgo
6+
- go mod download
7+
builds:
8+
- env:
9+
- CGO_ENABLED=0
10+
archives:
11+
- replacements:
12+
darwin: Darwin
13+
linux: Linux
14+
windows: Windows
15+
386: i386
16+
amd64: x86_64
17+
checksum:
18+
name_template: 'checksums.txt'
19+
snapshot:
20+
name_template: "{{ .Tag }}-next"
21+
changelog:
22+
sort: asc
23+
filters:
24+
exclude:
25+
- '^docs:'
26+
- '^test:'

cmd/backup.go

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io/ioutil"
9+
"os"
10+
"time"
11+
12+
"github.com/docker/docker/api/types/filters"
13+
"github.com/docker/docker/api/types/swarm"
14+
"github.com/pkg/errors"
15+
16+
"github.com/spf13/cobra"
17+
18+
dockertypes "github.com/docker/docker/api/types"
19+
dockerclient "github.com/docker/docker/client"
20+
)
21+
22+
type BackupStruct struct {
23+
Networks map[string]dockertypes.NetworkCreate
24+
Services map[string]swarm.ServiceSpec
25+
Secrets map[string]swarm.SecretSpec
26+
Configs map[string]swarm.ConfigSpec
27+
}
28+
29+
type Evacuation struct {
30+
cli *dockerclient.Client
31+
Backup BackupStruct
32+
}
33+
34+
func Backup(cmd *cobra.Command, args []string) {
35+
dest := args[0]
36+
37+
err := performBackup(dest)
38+
if err != nil {
39+
panic(err)
40+
}
41+
}
42+
43+
func performBackup(dest string) error {
44+
e := Evacuation{
45+
Backup: BackupStruct{},
46+
}
47+
48+
cli, err := dockerclient.NewEnvClient()
49+
if err != nil {
50+
return err
51+
}
52+
e.cli = cli
53+
54+
bg := context.Background()
55+
56+
networks, err := cli.NetworkList(bg, dockertypes.NetworkListOptions{})
57+
if err != nil {
58+
return err
59+
}
60+
61+
e.Backup.Networks = map[string]dockertypes.NetworkCreate{}
62+
networkIdNames := map[string]string{}
63+
skipNetworks := map[string]bool{"bridge": true, "docker_gwbridge": true, "ingress": true, "host": true, "none": true}
64+
65+
for _, network := range networks {
66+
spec := dockertypes.NetworkCreate{
67+
Driver: network.Driver,
68+
EnableIPv6: network.EnableIPv6,
69+
IPAM: &network.IPAM,
70+
Internal: network.Internal,
71+
Attachable: network.Attachable,
72+
Options: network.Options,
73+
Labels: network.Labels,
74+
}
75+
76+
if skipNetworks[network.Name] {
77+
continue
78+
}
79+
80+
networkIdNames[network.ID] = network.Name
81+
e.Backup.Networks[network.Name] = spec
82+
}
83+
84+
// services
85+
services, err := cli.ServiceList(bg, dockertypes.ServiceListOptions{})
86+
if err != nil {
87+
return err
88+
}
89+
e.Backup.Services = map[string]swarm.ServiceSpec{}
90+
91+
services, err = cli.ServiceList(bg, dockertypes.ServiceListOptions{})
92+
if err != nil {
93+
return err
94+
}
95+
e.Backup.Services = map[string]swarm.ServiceSpec{}
96+
97+
for _, service := range services {
98+
if service.Spec.Name == "evacuation" {
99+
continue
100+
}
101+
102+
serviceSpec := service.Spec
103+
for i, n := range serviceSpec.Networks {
104+
serviceSpec.Networks[i].Target = networkIdNames[n.Target]
105+
}
106+
107+
for i, n := range serviceSpec.TaskTemplate.Networks {
108+
serviceSpec.TaskTemplate.Networks[i].Target = networkIdNames[n.Target]
109+
}
110+
111+
for i, _ := range serviceSpec.TaskTemplate.ContainerSpec.Secrets {
112+
serviceSpec.TaskTemplate.ContainerSpec.Secrets[i].SecretID = ""
113+
}
114+
115+
for i, _ := range serviceSpec.TaskTemplate.ContainerSpec.Configs {
116+
serviceSpec.TaskTemplate.ContainerSpec.Configs[i].ConfigID = ""
117+
}
118+
119+
e.Backup.Services[serviceSpec.Name] = serviceSpec
120+
}
121+
122+
err = e.LoadSecretsData()
123+
if err != nil {
124+
return err
125+
}
126+
127+
bjson, err := json.MarshalIndent(e.Backup, "", " ")
128+
if err != nil {
129+
return err
130+
}
131+
132+
return ioutil.WriteFile(dest, bjson, os.FileMode(0600))
133+
}
134+
135+
func (e *Evacuation) LoadSecretsData() error {
136+
bg := context.Background()
137+
secretReferences := []*swarm.SecretReference{}
138+
139+
info, err := e.cli.Info(bg)
140+
if err != nil {
141+
return err
142+
}
143+
144+
// secrets
145+
e.Backup.Secrets = map[string]swarm.SecretSpec{}
146+
secrets, err := e.cli.SecretList(bg, dockertypes.SecretListOptions{})
147+
for _, secret := range secrets {
148+
e.Backup.Secrets[secret.Spec.Name] = secret.Spec
149+
secretReferences = append(secretReferences, &swarm.SecretReference{
150+
SecretName: secret.Spec.Name,
151+
SecretID: secret.ID,
152+
File: &swarm.SecretReferenceFileTarget{
153+
Name: secret.Spec.Name,
154+
UID: "0",
155+
GID: "0",
156+
Mode: os.FileMode(020),
157+
},
158+
})
159+
}
160+
161+
// configs
162+
e.Backup.Configs = map[string]swarm.ConfigSpec{}
163+
configs, err := e.cli.ConfigList(bg, dockertypes.ConfigListOptions{})
164+
for _, config := range configs {
165+
e.Backup.Configs[config.Spec.Name] = config.Spec
166+
}
167+
168+
loaderSpec := swarm.ServiceSpec{
169+
TaskTemplate: swarm.TaskSpec{
170+
Placement: &swarm.Placement{
171+
Constraints: []string{fmt.Sprintf("node.id==%s", info.Swarm.NodeID)},
172+
},
173+
ContainerSpec: &swarm.ContainerSpec{
174+
Image: "busybox",
175+
Command: []string{"sleep", "100000"},
176+
Secrets: secretReferences,
177+
},
178+
},
179+
}
180+
loaderSpec.Name = "evacuation"
181+
182+
_ = e.cli.ServiceRemove(bg, "evacuation")
183+
184+
service, err := e.cli.ServiceCreate(bg, loaderSpec, dockertypes.ServiceCreateOptions{})
185+
if err != nil {
186+
return err
187+
}
188+
189+
// "com.docker.swarm.service.name"
190+
containerFilter := filters.NewArgs()
191+
containerFilter.Add("label", fmt.Sprintf("com.docker.swarm.service.id=%s", service.ID))
192+
193+
var container *dockertypes.Container
194+
tries := 0
195+
196+
for {
197+
if tries > 10 {
198+
return errors.New("failed to create export container")
199+
}
200+
containers, _ := e.cli.ContainerList(bg, dockertypes.ContainerListOptions{Filters: containerFilter})
201+
if len(containers) > 0 {
202+
container = &containers[0]
203+
break
204+
}
205+
206+
tries += 1
207+
time.Sleep(time.Second * 5)
208+
}
209+
210+
fmt.Printf("evac container id: %s\n", container.ID)
211+
for i, s := range e.Backup.Secrets {
212+
fmt.Printf("loading %v\n", fmt.Sprintf("/run/secrets/%s", s.Name))
213+
214+
id, err := e.cli.ContainerExecCreate(bg, container.ID, dockertypes.ExecConfig{
215+
AttachStdout: true,
216+
Detach: false,
217+
Tty: true,
218+
Cmd: []string{"cat", fmt.Sprintf("/run/secrets/%s", s.Name)},
219+
})
220+
221+
if err != nil {
222+
return err
223+
}
224+
225+
containerConn, err := e.cli.ContainerExecAttach(bg, id.ID, dockertypes.ExecStartCheck{Detach: false, Tty: true})
226+
if err != nil {
227+
return err
228+
}
229+
230+
defer containerConn.Close()
231+
232+
buf := new(bytes.Buffer)
233+
234+
_, err = containerConn.Reader.WriteTo(buf)
235+
if err != nil {
236+
return err
237+
}
238+
239+
containerConn.Close()
240+
241+
s.Data = buf.Bytes()
242+
e.Backup.Secrets[i] = s
243+
}
244+
245+
return nil
246+
}

cmd/restore.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io/ioutil"
7+
8+
"github.com/docker/docker/api/types"
9+
"github.com/spf13/cobra"
10+
11+
dockerclient "github.com/docker/docker/client"
12+
)
13+
14+
func Restore(cmd *cobra.Command, args []string) {
15+
src := args[0]
16+
17+
err := performRestore(src)
18+
if err != nil {
19+
panic(err)
20+
}
21+
}
22+
23+
func performRestore(src string) error {
24+
b, err := ioutil.ReadFile(src)
25+
if err != nil {
26+
return err
27+
}
28+
29+
e := Evacuation{}
30+
cli, err := dockerclient.NewEnvClient()
31+
if err != nil {
32+
return err
33+
}
34+
e.cli = cli
35+
36+
bg := context.Background()
37+
38+
err = json.Unmarshal(b, &e.Backup)
39+
if err != nil {
40+
return err
41+
}
42+
43+
for name, net := range e.Backup.Networks {
44+
_, err := e.cli.NetworkCreate(bg, name, net)
45+
if err != nil {
46+
return err
47+
}
48+
}
49+
50+
secretNameId := map[string]string{}
51+
for n, s := range e.Backup.Secrets {
52+
r, err := e.cli.SecretCreate(bg, s)
53+
if err != nil {
54+
return err
55+
}
56+
57+
secretNameId[n] = r.ID
58+
}
59+
60+
configNameId := map[string]string{}
61+
for n, c := range e.Backup.Configs {
62+
r, err := e.cli.ConfigCreate(bg, c)
63+
if err != nil {
64+
return err
65+
}
66+
67+
configNameId[n] = r.ID
68+
}
69+
70+
for _, service := range e.Backup.Services {
71+
for is, secret := range service.TaskTemplate.ContainerSpec.Secrets {
72+
service.TaskTemplate.ContainerSpec.Secrets[is].SecretID = secretNameId[secret.SecretName]
73+
}
74+
for ic, config := range service.TaskTemplate.ContainerSpec.Configs {
75+
service.TaskTemplate.ContainerSpec.Configs[ic].ConfigID = configNameId[config.ConfigName]
76+
}
77+
78+
_, err = e.cli.ServiceCreate(bg, service, types.ServiceCreateOptions{})
79+
if err != nil {
80+
return err
81+
}
82+
}
83+
84+
return nil
85+
}

go.mod

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module swarm-backup
2+
3+
require (
4+
github.com/Microsoft/go-winio v0.4.12 // indirect
5+
github.com/davecgh/go-spew v1.1.1
6+
github.com/docker/distribution v2.7.1+incompatible // indirect
7+
github.com/docker/docker v1.13.1
8+
github.com/docker/go-connections v0.4.0 // indirect
9+
github.com/docker/go-units v0.4.0 // indirect
10+
github.com/gogo/protobuf v1.2.1 // indirect
11+
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
12+
github.com/opencontainers/image-spec v1.0.1 // indirect
13+
github.com/pkg/errors v0.8.1
14+
github.com/spf13/cobra v0.0.4
15+
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
16+
)
17+
18+
replace github.com/docker/docker v1.13.1 => github.com/docker/engine v0.0.0-20180718150940-a3ef7e9a9bda

0 commit comments

Comments
 (0)