-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Vasiliy Ostanin
committed
May 31, 2019
0 parents
commit 20b415b
Showing
8 changed files
with
498 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
dist/ | ||
*.iml | ||
.idea | ||
*.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.