feat: add backup support for Code Flow

Add support for taking on demand backups of Code Flow.
Resolves flow/services#7
This commit is contained in:
Dan Anglin 2023-12-17 08:51:11 +00:00
parent ab0f2029b5
commit ee910722cb
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
10 changed files with 201 additions and 45 deletions

View file

@ -35,8 +35,9 @@ The repository manages the services hosted on Flow Platform with the use of http
----
$ mage -l
Targets:
backup creates a backup of the specified service.
clean cleans the workspace.
deploy deploys the services to the Flow Platform.
deploy deploys the service(s) to the Flow Platform.
prepare prepares the service's build directory.
shutdown shuts down the Flow Platform in a given environment.
stop stops a running service within the Flow Platform.
@ -59,3 +60,27 @@ mage prepare production forgejo
----
mage deploy production forgejo
----
== Backup Guide
There is support for backing up the data for some of the services, e.g. `forgejo`. You can use mage to run the backup for a specific service.
[source,console]
----
mage backup production forgejo
----
Here mage will stop the running service and recreate the container running the specific backup script for that service. If there is no backup support for a particular service mage will inform you of this.
[source,console]
----
$ mage backup production traefik
Backup is not supported for "traefik".
----
Currently the service will not automatically resume after the backup is completed but you can achieve this with a chain command.
[source,console]
----
mage backup production forgejo && mage deploy production forgejo
----

2
config

@ -1 +1 @@
Subproject commit d332a51218fc2b5cb152fd2439df061940c07dab
Subproject commit 1766c5ec179d869fc4999523cba036b75e0b4fd7

View file

@ -0,0 +1,47 @@
package actions
import (
"fmt"
"flow/services/internal/config"
"flow/services/internal/services"
)
const containerRunModeBackup = "backup"
func Backup(environment, service string) error {
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
switch service {
case services.Forgejo:
cfg.Forgejo.ContainerRunMode = containerRunModeBackup
default:
fmt.Printf("Backup is not supported for %q.\n", service)
return nil
}
if err := backup(cfg, environment, service); err != nil {
return fmt.Errorf("error performing a backup of %q; %w", service, err)
}
return nil
}
func backup(cfg config.Config, environment, service string) error {
if err := stop(cfg.Docker.Host, environment, service); err != nil {
return fmt.Errorf("unable to stop %q; %w", service, err)
}
if err := prepareCompose(cfg, environment); err != nil {
return fmt.Errorf("unable to regenerate the compose file for the backup; %w", err)
}
if err := deploy(cfg.Docker.Host, environment, service, false); err != nil {
return fmt.Errorf("error deploying the backup container; %w", err)
}
return nil
}

View file

@ -2,13 +2,27 @@ package actions
import (
"flow/services/internal"
"flow/services/internal/config"
"fmt"
"os"
"github.com/magefile/mage/sh"
)
func Deploy(dockerHost, environment, service string, daemon bool) error {
func Deploy(environment, service string) error {
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
if err := deploy(cfg.Docker.Host, environment, service, true); err != nil {
return fmt.Errorf("error deploying %q; %w", service, err)
}
return nil
}
func deploy(dockerHost, environment, service string, daemon bool) error {
os.Setenv("DOCKER_HOST", dockerHost)
command := []string{

View file

@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
"text/template"
"time"
"flow/services/internal"
"flow/services/internal/bundle"
@ -141,6 +142,7 @@ func render(cfg config.Config, environment, component string) error {
funcMap := template.FuncMap{
"default": defaultValue,
"timestamp": timestamp(),
}
for _, f := range files {
@ -246,3 +248,11 @@ func defaultValue(value, defaultValue string) string {
return value
}
func timestamp() func() string {
now := time.Now().Format("20060102-1504")
return func() string {
return now
}
}

View file

@ -0,0 +1,38 @@
package actions
import (
"fmt"
"os"
"flow/services/internal"
"flow/services/internal/config"
"github.com/magefile/mage/sh"
)
func Shutdown(environment string) error {
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
if err := shutdown(cfg.Docker.Host, environment); err != nil {
return fmt.Errorf("unable to shutdown the %q environment; %w", environment, err)
}
return nil
}
func shutdown(dockerHost, environment string) error {
os.Setenv("DOCKER_HOST", dockerHost)
command := []string{
"docker",
"compose",
"--project-directory",
fmt.Sprintf("%s/%s/compose", internal.RootBuildDir, environment),
"down",
}
return sh.RunV(command[0], command[1:]...)
}

View file

@ -2,13 +2,27 @@ package actions
import (
"flow/services/internal"
"flow/services/internal/config"
"fmt"
"os"
"github.com/magefile/mage/sh"
)
func Stop(dockerHost, environment, service string) error {
func Stop(environment, service string) error {
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
if err := stop(cfg.Docker.Host, environment, service); err != nil {
return fmt.Errorf("unable to stop %q; %w", service, err)
}
return nil
}
func stop(dockerHost, environment, service string) error {
os.Setenv("DOCKER_HOST", dockerHost)
command := []string{

View file

@ -78,6 +78,8 @@ type Forgejo struct {
Oauth2JwtSigningAlgo string `json:"oauth2JwtSigningAlgo"`
Oauth2JwtSecret string `json:"oauth2JwtSecret"`
Actions ForgejoActions `json:"actions"`
Backups ForgejoBackups `json:"backups"`
ContainerRunMode string
}
type ForgejoActions struct {
@ -85,6 +87,10 @@ type ForgejoActions struct {
DefaultActionsURL string `json:"defaultActionsURL"`
}
type ForgejoBackups struct {
ContainerDirectory string `json:"containerDirectory"`
}
type Gotosocial struct {
Version string `json:"version"`
Name string `json:"name"`
@ -139,21 +145,21 @@ type LandingPageLinks struct {
}
func NewConfig(environment string) (Config, error) {
var c Config
var cfg Config
path := filepath.Join(configDir, environment, configFileName)
f, err := os.Open(path)
if err != nil {
return c, fmt.Errorf("unable to open the file; %w", err)
return cfg, fmt.Errorf("unable to open the file; %w", err)
}
defer f.Close()
decoder := json.NewDecoder(f)
if err = decoder.Decode(&c); err != nil {
return c, fmt.Errorf("unable to decode JSON data; %w", err)
if err = decoder.Decode(&cfg); err != nil {
return cfg, fmt.Errorf("unable to decode JSON data; %w", err)
}
return c, nil
return cfg, nil
}

View file

@ -5,7 +5,6 @@ package main
import (
"flow/services/internal"
"flow/services/internal/actions"
"flow/services/internal/config"
"fmt"
"os"
"path/filepath"
@ -14,6 +13,15 @@ import (
"github.com/magefile/mage/sh"
)
// Backup creates a backup of the specified service.
func Backup(environment, service string) error {
if err := actions.Backup(environment, service); err != nil {
return fmt.Errorf("error running Backup for %q; %w", service, err)
}
return nil
}
// Clean cleans the workspace.
func Clean(environment string) error {
buildDir := filepath.Join(internal.RootBuildDir, environment)
@ -42,13 +50,8 @@ func Deploy(environment, service string) error {
mg.F(Prepare, environment, service),
)
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
if err := actions.Deploy(cfg.Docker.Host, environment, service, true); err != nil {
return fmt.Errorf("error deploying %q; %w", service, err)
if err := actions.Deploy(environment, service); err != nil {
return fmt.Errorf("error running Deploy for %q; %w", service, err)
}
return nil
@ -57,7 +60,7 @@ func Deploy(environment, service string) error {
// Prepare prepares the service's build directory.
func Prepare(environment, service string) error {
if err := actions.Prepare(environment, service); err != nil {
return fmt.Errorf("error running preparations; %w", err)
return fmt.Errorf("error running Prepare for %q; %w", service, err)
}
return nil
@ -65,33 +68,17 @@ func Prepare(environment, service string) error {
// Shutdown shuts down the Flow Platform in a given environment.
func Shutdown(environment string) error {
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
os.Setenv("DOCKER_HOST", cfg.Docker.Host)
command := []string{
"docker",
"compose",
"--project-directory",
fmt.Sprintf("%s/%s/compose", internal.RootBuildDir, environment),
"down",
}
return sh.RunV(command[0], command[1:]...)
}
// Stop stops a running service within the Flow Platform.
func Stop(environment, service string) error {
cfg, err := config.NewConfig(environment)
if err != nil {
return fmt.Errorf("unable to load the configuration; %w", err)
}
if err := actions.Stop(cfg.Docker.Host, environment, service); err != nil {
return fmt.Errorf("error stopping %q; %w", service, err)
if err := actions.Shutdown(environment); err != nil {
return fmt.Errorf("error running Shutdown for %q; %w", environment, err)
}
return nil
}
// Stop stops a running service within the Flow Platform.
func Stop(environment, service string) error {
if err := actions.Stop(environment, service); err != nil {
return fmt.Errorf("error running Stop for %q; %w", service, err)
}
return nil

View file

@ -55,13 +55,28 @@ services:
image: "localhost/flow/forgejo:{{ .Forgejo.Version }}"
build:
context: "../forgejo"
{{- if eq .Forgejo.ContainerRunMode "backup" }}
command:
- forgejo
- dump
- --config
- {{ .Forgejo.AppIni }}
- --file
- {{ .Forgejo.Backups.ContainerDirectory }}/forgejo-backup-v{{ .Forgejo.Version }}-{{ timestamp }}.tar.gz
- --type
- tar.gz
{{- end }}
expose:
- "{{ .Forgejo.SshPort }}"
- "{{ .Forgejo.HttpPort }}"
networks:
flow:
ipv4_address: "{{ .Forgejo.ContainerIpv4Address }}"
{{- if eq .Forgejo.ContainerRunMode "backup" }}
restart: "no"
{{- else }}
restart: "always"
{{- end }}
volumes:
{{- template "defaultVolumes" }}
# Forgejo data volume