From 8b110fd7897283394d7abf0cb786ad9285b564ce Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Sat, 21 Aug 2021 19:26:42 +0200 Subject: [PATCH] scaffold script flow --- Dockerfile | 4 +- src/main.go | 215 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 159 insertions(+), 60 deletions(-) diff --git a/Dockerfile b/Dockerfile index 883fe74..65090fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,7 @@ FROM alpine:3.14 WORKDIR /root -RUN apk add --update ca-certificates docker openrc gnupg -RUN update-ca-certificates -RUN rc-update add docker boot +RUN apk add --update ca-certificates COPY --from=builder /app/backup /usr/bin/backup diff --git a/src/main.go b/src/main.go index 931ed5a..6e86036 100644 --- a/src/main.go +++ b/src/main.go @@ -2,8 +2,10 @@ package main import ( "context" + "errors" "fmt" "os" + "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -13,82 +15,181 @@ import ( ) func main() { - if err := godotenv.Load("/etc/backup.env"); err != nil { + s := &script{} + defer func() { + for _, thunk := range s.cleanupTasks { + thunk() + } + }() + + s.lock() + defer s.unlock() + + if err := s.init(); err != nil { panic(err) } - ctx := context.Background() - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { + if err := s.stopContainers(); err != nil { panic(err) } + if err := s.takeBackup(); err != nil { + panic(err) + } + + if err := s.restartContainers(); err != nil { + panic(err) + } + + if err := s.encryptBackup(); err != nil { + panic(err) + } + + if err := s.copyBackup(); err != nil { + panic(err) + } + + if err := s.cleanBackup(); err != nil { + panic(err) + } + + if err := s.pruneBackups(); err != nil { + panic(err) + } +} + +type script struct { + ctx context.Context + cli *client.Client + stoppedContainers []types.Container + file string + cleanupTasks []func() +} + +func (s *script) lock() {} +func (s *script) unlock() {} + +func (s *script) init() error { + s.ctx = context.Background() + if timeout, ok := os.LookupEnv("BACKUP_TIMEOUT_DURATION"); ok { + d, err := time.ParseDuration(timeout) + if err != nil { + return fmt.Errorf("init: error parsing given timeout duration: %w", err) + } + withTimeout, cancelFunc := context.WithTimeout(context.Background(), d) + s.ctx = withTimeout + s.cleanupTasks = append(s.cleanupTasks, cancelFunc) + } + + if err := godotenv.Load("/etc/backup.env"); err != nil { + return fmt.Errorf("init: failed to load env file: %w", err) + } + socketExists, err := fileExists("/var/run/docker.sock") if err != nil { - panic(err) + return fmt.Errorf("init: error checking whether docker.sock is available: %w", err) } - - var containersToStop []types.Container if socketExists { - containersToStop, err = cli.ContainerList(ctx, types.ContainerListOptions{ - Quiet: true, - Filters: filters.NewArgs(filters.KeyValuePair{ - Key: "label", - Value: fmt.Sprintf("docker-volume-backup.stop-during-backup=%s", os.Getenv("BACKUP_STOP_CONTAINER_LABEL")), - }), - }) + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { + return fmt.Errorf("init: failied to create docker client") + } + s.cli = cli + } + return nil +} + +func (s *script) stopContainers() error { + if s.cli == nil { + return nil + } + stoppedContainers, err := s.cli.ContainerList(s.ctx, types.ContainerListOptions{ + Quiet: true, + Filters: filters.NewArgs(filters.KeyValuePair{ + Key: "label", + Value: fmt.Sprintf("docker-volume-backup.stop-during-backup=%s", os.Getenv("BACKUP_STOP_CONTAINER_LABEL")), + }), + }) + + s.stoppedContainers = stoppedContainers + if err != nil { + return fmt.Errorf("stopContainers: error querying for containers to stop: %w", err) + } + fmt.Printf("Stopping %d containers\n", len(s.stoppedContainers)) + + if len(s.stoppedContainers) != 0 { + fmt.Println("Stopping containers") + for _, container := range s.stoppedContainers { + if err := s.cli.ContainerStop(s.ctx, container.ID, nil); err != nil { + return fmt.Errorf("stopContainers: error stopping container %s: %w", container.Names[0], err) + } + } + } + return nil +} + +func (s *script) takeBackup() error { + return errors.New("takeBackup: not implemented yet") +} + +func (s *script) restartContainers() error { + fmt.Println("Starting containers/services back up") + servicesRequiringUpdate := map[string]struct{}{} + for _, container := range s.stoppedContainers { + if swarmServiceName, ok := container.Labels["com.docker.swarm.service.name"]; ok { + servicesRequiringUpdate[swarmServiceName] = struct{}{} + continue + } + if err := s.cli.ContainerStart(s.ctx, container.ID, types.ContainerStartOptions{}); err != nil { panic(err) } - fmt.Printf("Stopping %d containers\n", len(containersToStop)) } - if len(containersToStop) != 0 { - fmt.Println("Stopping containers") - for _, container := range containersToStop { - if err := cli.ContainerStop(ctx, container.ID, nil); err != nil { - panic(err) - } - } - } - - fmt.Println("Creating backup") - // TODO: Implement backup - - if len(containersToStop) != 0 { - fmt.Println("Starting containers/services back up") - servicesRequiringUpdate := map[string]struct{}{} - for _, container := range containersToStop { - if swarmServiceName, ok := container.Labels["com.docker.swarm.service.name"]; ok { - servicesRequiringUpdate[swarmServiceName] = struct{}{} - continue - } - if err := cli.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { - panic(err) - } - } - - if len(servicesRequiringUpdate) != 0 { - services, _ := cli.ServiceList(ctx, types.ServiceListOptions{}) - for serviceName := range servicesRequiringUpdate { - var serviceMatch swarm.Service - for _, service := range services { - if service.Spec.Name == serviceName { - serviceMatch = service - break - } + if len(servicesRequiringUpdate) != 0 { + services, _ := s.cli.ServiceList(s.ctx, types.ServiceListOptions{}) + for serviceName := range servicesRequiringUpdate { + var serviceMatch swarm.Service + for _, service := range services { + if service.Spec.Name == serviceName { + serviceMatch = service + break } - if serviceMatch.ID == "" { - panic(fmt.Sprintf("Couldn't find service with name %s", serviceName)) - } - serviceMatch.Spec.TaskTemplate.ForceUpdate = 1 - cli.ServiceUpdate( - ctx, serviceMatch.ID, - serviceMatch.Version, serviceMatch.Spec, types.ServiceUpdateOptions{}, - ) } + if serviceMatch.ID == "" { + return fmt.Errorf("restartContainers: Couldn't find service with name %s", serviceName) + } + serviceMatch.Spec.TaskTemplate.ForceUpdate = 1 + s.cli.ServiceUpdate( + s.ctx, serviceMatch.ID, + serviceMatch.Version, serviceMatch.Spec, types.ServiceUpdateOptions{}, + ) } } + return nil +} + +func (s *script) encryptBackup() error { + _, ok := os.LookupEnv("GPG_PASSPHRASE") + if !ok { + return nil + } + return errors.New("encryptBackup: not implemented yet") +} + +func (s *script) copyBackup() error { + return errors.New("copyBackup: not implemented yet") +} + +func (s *script) cleanBackup() error { + return errors.New("cleanBackup: not implemented yet") +} + +func (s *script) pruneBackups() error { + _, ok := os.LookupEnv("BACKUP_RETENTION_DAYS") + if !ok { + return nil + } + return errors.New("pruneBackups: not implemented yet") } func fileExists(location string) (bool, error) {