Skip to content

Commit

Permalink
import github.com/databacker/api instead of defining locally
Browse files Browse the repository at this point in the history
Signed-off-by: Avi Deitcher <avi@deitcher.net>
  • Loading branch information
deitch committed Nov 10, 2024
1 parent e44221e commit bc37a91
Show file tree
Hide file tree
Showing 35 changed files with 957 additions and 963 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.22'
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.22
- name: Build for all platforms
run: |
make build-all
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# mysql backup image
FROM golang:1.21.13-alpine3.20 AS build
FROM golang:1.22.9-alpine3.20 AS build

COPY . /src/mysql-backup
WORKDIR /src/mysql-backup
Expand Down
7 changes: 4 additions & 3 deletions cmd/common_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"reflect"

"github.com/databacker/mysql-backup/pkg/core"
Expand All @@ -19,17 +20,17 @@ func newMockExecs() *mockExecs {
return m
}

func (m *mockExecs) Dump(opts core.DumpOptions) (core.DumpResults, error) {
func (m *mockExecs) Dump(ctx context.Context, opts core.DumpOptions) (core.DumpResults, error) {
args := m.Called(opts)
return core.DumpResults{}, args.Error(0)
}

func (m *mockExecs) Restore(opts core.RestoreOptions) error {
func (m *mockExecs) Restore(ctx context.Context, opts core.RestoreOptions) error {
args := m.Called(opts)
return args.Error(0)
}

func (m *mockExecs) Prune(opts core.PruneOptions) error {
func (m *mockExecs) Prune(ctx context.Context, opts core.PruneOptions) error {
args := m.Called(opts)
return args.Error(0)
}
Expand Down
211 changes: 137 additions & 74 deletions cmd/dump.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package cmd

import (
"context"
"fmt"
"strings"

"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.opentelemetry.io/otel/codes"

"github.com/databacker/api/go/api"
"github.com/databacker/mysql-backup/pkg/compression"
"github.com/databacker/mysql-backup/pkg/core"
"github.com/databacker/mysql-backup/pkg/storage"
"github.com/databacker/mysql-backup/pkg/util"
)

const (
Expand Down Expand Up @@ -38,92 +42,84 @@ func dumpCmd(passedExecs execs, cmdConfig *cmdConfiguration) (*cobra.Command, er
bindFlags(cmd, v)
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
// this is the tracer that we will use throughout the entire run
tracer := getTracer("dump")
ctx = util.ContextWithTracer(ctx, tracer)
_, startupSpan := tracer.Start(ctx, "startup")
cmdConfig.logger.Debug("starting dump")
defer func() {
tp := getTracerProvider()
tp.ForceFlush(ctx)
_ = tp.Shutdown(ctx)
}()
// check targets
targetURLs := v.GetStringSlice("target")
var (
targets []storage.Storage
err error
dumpConfig *api.Dump
scriptsConfig *api.Scripts
)
if len(targetURLs) > 0 {
for _, t := range targetURLs {
store, err := storage.ParseURL(t, cmdConfig.creds)
if err != nil {
return fmt.Errorf("invalid target url: %v", err)
}
targets = append(targets, store)
}
} else {
// try the config file
if cmdConfig.configuration != nil {
// parse the target objects, then the ones listed for the backup
targetStructures := cmdConfig.configuration.Targets
dumpTargets := cmdConfig.configuration.Dump.Targets
for _, t := range dumpTargets {
var store storage.Storage
if target, ok := targetStructures[t]; !ok {
return fmt.Errorf("target %s from dump configuration not found in targets configuration", t)
} else {
store, err = target.Storage.Storage()
if err != nil {
return fmt.Errorf("target %s from dump configuration has invalid URL: %v", t, err)
}
}
targets = append(targets, store)
}
if cmdConfig.configuration != nil {
dumpConfig = cmdConfig.configuration.Dump
if dumpConfig != nil {
scriptsConfig = dumpConfig.Scripts
}
}
targets, err := parseTargets(targetURLs, cmdConfig)
if err != nil {
return fmt.Errorf("error parsing targets: %v", err)
}
if len(targets) == 0 {
return fmt.Errorf("no targets specified")
}
safechars := v.GetBool("safechars")
if !v.IsSet("safechars") && cmdConfig.configuration != nil {
safechars = cmdConfig.configuration.Dump.Safechars
if !v.IsSet("safechars") && dumpConfig != nil && dumpConfig.Safechars != nil {
safechars = *dumpConfig.Safechars
}
include := v.GetStringSlice("include")
if len(include) == 0 && cmdConfig.configuration != nil {
include = cmdConfig.configuration.Dump.Include
if len(include) == 0 && dumpConfig != nil && dumpConfig.Include != nil {
include = *dumpConfig.Include
}
// make this slice nil if it's empty, so it is consistent; used mainly for test consistency
if len(include) == 0 {
include = nil
}
exclude := v.GetStringSlice("exclude")
if len(exclude) == 0 && cmdConfig.configuration != nil {
exclude = cmdConfig.configuration.Dump.Exclude
if len(exclude) == 0 && dumpConfig != nil && dumpConfig.Exclude != nil {
exclude = *dumpConfig.Exclude
}
// make this slice nil if it's empty, so it is consistent; used mainly for test consistency
if len(exclude) == 0 {
exclude = nil
}
preBackupScripts := v.GetString("pre-backup-scripts")
if preBackupScripts == "" && cmdConfig.configuration != nil {
preBackupScripts = cmdConfig.configuration.Dump.Scripts.PreBackup
if preBackupScripts == "" && scriptsConfig != nil && scriptsConfig.PreBackup != nil {
preBackupScripts = *scriptsConfig.PreBackup
}
postBackupScripts := v.GetString("post-backup-scripts")
if postBackupScripts == "" && cmdConfig.configuration != nil {
postBackupScripts = cmdConfig.configuration.Dump.Scripts.PostBackup
if postBackupScripts == "" && scriptsConfig != nil && scriptsConfig.PostBackup != nil {
postBackupScripts = *scriptsConfig.PostBackup
}
noDatabaseName := v.GetBool("no-database-name")
if !v.IsSet("no-database-name") && cmdConfig.configuration != nil {
noDatabaseName = cmdConfig.configuration.Dump.NoDatabaseName
if !v.IsSet("no-database-name") && dumpConfig != nil && dumpConfig.NoDatabaseName != nil {
noDatabaseName = *dumpConfig.NoDatabaseName
}
compact := v.GetBool("compact")
if !v.IsSet("compact") && cmdConfig.configuration != nil {
compact = cmdConfig.configuration.Dump.Compact
if !v.IsSet("compact") && dumpConfig != nil && dumpConfig.Compact != nil {
compact = *dumpConfig.Compact
}
maxAllowedPacket := v.GetInt("max-allowed-packet")
if !v.IsSet("max-allowed-packet") && cmdConfig.configuration != nil && cmdConfig.configuration.Dump.MaxAllowedPacket != 0 {
maxAllowedPacket = cmdConfig.configuration.Dump.MaxAllowedPacket
if !v.IsSet("max-allowed-packet") && dumpConfig != nil && dumpConfig.MaxAllowedPacket != nil && *dumpConfig.MaxAllowedPacket != 0 {
maxAllowedPacket = *dumpConfig.MaxAllowedPacket
}

// compression algorithm: check config, then CLI/env var overrides
var (
compressionAlgo string
compressor compression.Compressor
)
if cmdConfig.configuration != nil {
compressionAlgo = cmdConfig.configuration.Dump.Compression
if cmdConfig.configuration != nil && dumpConfig.Compression != nil {
compressionAlgo = *dumpConfig.Compression
}
compressionVar := v.GetString("compression")
if compressionVar != "" {
Expand All @@ -138,41 +134,21 @@ func dumpCmd(passedExecs execs, cmdConfig *cmdConfiguration) (*cobra.Command, er

// retention, if enabled
retention := v.GetString("retention")
if retention == "" && cmdConfig.configuration != nil {
retention = cmdConfig.configuration.Prune.Retention
if retention == "" && cmdConfig.configuration != nil && cmdConfig.configuration.Prune.Retention != nil {
retention = *cmdConfig.configuration.Prune.Retention
}
filenamePattern := v.GetString("filename-pattern")

if !v.IsSet("filename-pattern") && cmdConfig.configuration != nil {
filenamePattern = cmdConfig.configuration.Dump.FilenamePattern
if !v.IsSet("filename-pattern") && dumpConfig != nil && dumpConfig.FilenamePattern != nil {
filenamePattern = *dumpConfig.FilenamePattern
}
if filenamePattern == "" {
filenamePattern = defaultFilenamePattern
}

// timer options
once := v.GetBool("once")
if !v.IsSet("once") && cmdConfig.configuration != nil {
once = cmdConfig.configuration.Dump.Schedule.Once
}
cron := v.GetString("cron")
if cron == "" && cmdConfig.configuration != nil {
cron = cmdConfig.configuration.Dump.Schedule.Cron
}
begin := v.GetString("begin")
if begin == "" && cmdConfig.configuration != nil {
begin = cmdConfig.configuration.Dump.Schedule.Begin
}
frequency := v.GetInt("frequency")
if frequency == 0 && cmdConfig.configuration != nil {
frequency = cmdConfig.configuration.Dump.Schedule.Frequency
}
timerOpts := core.TimerOptions{
Once: once,
Cron: cron,
Begin: begin,
Frequency: frequency,
}
timerOpts := parseTimerOptions(v, cmdConfig.configuration)

var executor execs
executor = &core.Executor{}
if passedExecs != nil {
Expand All @@ -182,7 +158,14 @@ func dumpCmd(passedExecs execs, cmdConfig *cmdConfiguration) (*cobra.Command, er

// at this point, any errors should not have usage
cmd.SilenceUsage = true

// done with the startup
startupSpan.End()

if err := executor.Timer(timerOpts, func() error {
// start a new span for the dump, should not be a child of the startup one
tracerCtx, dumpSpan := tracer.Start(ctx, "run")
defer dumpSpan.End()
uid := uuid.New()
dumpOpts := core.DumpOptions{
Targets: targets,
Expand All @@ -199,15 +182,18 @@ func dumpCmd(passedExecs execs, cmdConfig *cmdConfiguration) (*cobra.Command, er
Run: uid,
FilenamePattern: filenamePattern,
}
_, err := executor.Dump(dumpOpts)
_, err := executor.Dump(tracerCtx, dumpOpts)
if err != nil {
dumpSpan.SetStatus(codes.Error, fmt.Sprintf("error running dump: %v", err))
return fmt.Errorf("error running dump: %w", err)
}
if retention != "" {
if err := executor.Prune(core.PruneOptions{Targets: targets, Retention: retention}); err != nil {
if err := executor.Prune(tracerCtx, core.PruneOptions{Targets: targets, Retention: retention}); err != nil {
dumpSpan.SetStatus(codes.Error, fmt.Sprintf("error running prune: %v", err))
return fmt.Errorf("error running prune: %w", err)
}
}
dumpSpan.SetStatus(codes.Ok, "dump complete")
return nil
}); err != nil {
return fmt.Errorf("error running command: %w", err)
Expand Down Expand Up @@ -278,3 +264,80 @@ S3: If it is a URL of the format s3://bucketname/path then it will connect via S

return cmd, nil
}

func parseTimerOptions(v *viper.Viper, config *api.ConfigSpec) core.TimerOptions {
var scheduleConfig *api.Schedule
if config != nil {
dumpConfig := config.Dump
if dumpConfig != nil {
scheduleConfig = dumpConfig.Schedule
}
}
once := v.GetBool("once")
if !v.IsSet("once") && scheduleConfig != nil && scheduleConfig.Once != nil {
once = *scheduleConfig.Once
}
cron := v.GetString("cron")
if cron == "" && scheduleConfig != nil && scheduleConfig.Cron != nil {
cron = *scheduleConfig.Cron
}
begin := v.GetString("begin")
if begin == "" && scheduleConfig != nil && scheduleConfig.Begin != nil {
begin = fmt.Sprintf("%d", *scheduleConfig.Begin)
}
frequency := v.GetInt("frequency")
if frequency == 0 && scheduleConfig != nil && scheduleConfig.Frequency != nil {
frequency = *scheduleConfig.Frequency
}
return core.TimerOptions{
Once: once,
Cron: cron,
Begin: begin,
Frequency: frequency,
}

}

func parseTargets(urls []string, cmdConfig *cmdConfiguration) ([]storage.Storage, error) {
var targets []storage.Storage
if len(urls) > 0 {
for _, t := range urls {
store, err := storage.ParseURL(t, cmdConfig.creds)
if err != nil {
return nil, fmt.Errorf("invalid target url: %v", err)
}
targets = append(targets, store)
}
} else {
// try the config file
if cmdConfig.configuration != nil {
// parse the target objects, then the ones listed for the backup
var (
targetStructures map[string]api.Target
dumpTargets []string
)
if cmdConfig.configuration.Targets != nil {
targetStructures = *cmdConfig.configuration.Targets
}
if cmdConfig.configuration != nil && cmdConfig.configuration.Dump != nil && cmdConfig.configuration.Dump.Targets != nil {
dumpTargets = *cmdConfig.configuration.Dump.Targets
}
for _, t := range dumpTargets {
var (
store storage.Storage
err error
)
if target, ok := targetStructures[t]; !ok {
return nil, fmt.Errorf("target %s from dump configuration not found in targets configuration", t)
} else {
store, err = storage.FromTarget(target)
if err != nil {
return nil, fmt.Errorf("target %s from dump configuration has invalid URL: %v", t, err)
}
}
targets = append(targets, store)
}
}
}
return targets, nil
}
Loading

0 comments on commit bc37a91

Please sign in to comment.