scaffold script flow

This commit is contained in:
Frederik Ring 2021-08-21 19:26:42 +02:00
parent efb52aa806
commit 8b110fd789
2 changed files with 159 additions and 60 deletions

View File

@ -12,9 +12,7 @@ FROM alpine:3.14
WORKDIR /root WORKDIR /root
RUN apk add --update ca-certificates docker openrc gnupg RUN apk add --update ca-certificates
RUN update-ca-certificates
RUN rc-update add docker boot
COPY --from=builder /app/backup /usr/bin/backup COPY --from=builder /app/backup /usr/bin/backup

View File

@ -2,8 +2,10 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"os" "os"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -13,82 +15,181 @@ import (
) )
func main() { 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) panic(err)
} }
ctx := context.Background() if err := s.stopContainers(); err != nil {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err) 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") socketExists, err := fileExists("/var/run/docker.sock")
if err != nil { if err != nil {
panic(err) return fmt.Errorf("init: error checking whether docker.sock is available: %w", err)
} }
var containersToStop []types.Container
if socketExists { if socketExists {
containersToStop, err = cli.ContainerList(ctx, types.ContainerListOptions{ cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
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")),
}),
})
if err != nil { 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) panic(err)
} }
fmt.Printf("Stopping %d containers\n", len(containersToStop))
} }
if len(containersToStop) != 0 { if len(servicesRequiringUpdate) != 0 {
fmt.Println("Stopping containers") services, _ := s.cli.ServiceList(s.ctx, types.ServiceListOptions{})
for _, container := range containersToStop { for serviceName := range servicesRequiringUpdate {
if err := cli.ContainerStop(ctx, container.ID, nil); err != nil { var serviceMatch swarm.Service
panic(err) for _, service := range services {
} if service.Spec.Name == serviceName {
} serviceMatch = service
} break
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 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) { func fileExists(location string) (bool, error) {