Skip to content

Commit

Permalink
feat!: redefine hostname as a required property that maps to --host (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed May 5, 2024
1 parent f6d5837 commit 4847010
Show file tree
Hide file tree
Showing 21 changed files with 386 additions and 300 deletions.
382 changes: 192 additions & 190 deletions gen/go/v1/config.pb.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/api/backresthandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (s *BackrestHandler) AddRepo(ctx context.Context, req *connect.Request[v1.R
return nil, fmt.Errorf("failed to find or install restic binary: %w", err)
}

r, err := repo.NewRepoOrchestrator(req.Msg, bin)
r, err := repo.NewRepoOrchestrator(c, req.Msg, bin)
if err != nil {
return nil, fmt.Errorf("failed to configure repo: %w", err)
}
Expand Down
12 changes: 8 additions & 4 deletions internal/api/backresthandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func TestBackup(t *testing.T) {

sut := createSystemUnderTest(t, &config.MemoryStore{
Config: &v1.Config{
Modno: 1234,
Modno: 1234,
Instance: "test",
Repos: []*v1.Repo{
{
Id: "local",
Expand Down Expand Up @@ -177,7 +178,8 @@ func TestMultipleBackup(t *testing.T) {

sut := createSystemUnderTest(t, &config.MemoryStore{
Config: &v1.Config{
Modno: 1234,
Modno: 1234,
Instance: "test",
Repos: []*v1.Repo{
{
Id: "local",
Expand Down Expand Up @@ -245,7 +247,8 @@ func TestHookExecution(t *testing.T) {

sut := createSystemUnderTest(t, &config.MemoryStore{
Config: &v1.Config{
Modno: 1234,
Modno: 1234,
Instance: "test",
Repos: []*v1.Repo{
{
Id: "local",
Expand Down Expand Up @@ -415,7 +418,8 @@ func TestRestore(t *testing.T) {

sut := createSystemUnderTest(t, &config.MemoryStore{
Config: &v1.Config{
Modno: 1234,
Modno: 1234,
Instance: "test",
Repos: []*v1.Repo{
{
Id: "local",
Expand Down
26 changes: 3 additions & 23 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package config
import (
"errors"
"fmt"
"os"
"path"
"sync"

v1 "github.com/garethgeorge/backrest/gen/go/v1"
Expand All @@ -20,31 +18,13 @@ type ConfigStore interface {
}

func NewDefaultConfig() *v1.Config {
hostname, _ := os.Hostname()
return &v1.Config{
Host: hostname,
Repos: []*v1.Repo{},
Plans: []*v1.Plan{},
Instance: "",
Repos: []*v1.Repo{},
Plans: []*v1.Plan{},
}
}

func configDir(override string) string {
if override != "" {
return override
}

if env := os.Getenv("XDG_CONFIG_HOME"); env != "" {
return path.Join(env, "backrest")
}

home, err := os.UserHomeDir()
if err != nil {
panic(err)
}

return fmt.Sprintf("%v/.config/backrest", home)
}

type CachingValidatingStore struct {
ConfigStore
mu sync.Mutex
Expand Down
6 changes: 5 additions & 1 deletion internal/config/migrations/migrations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package migrations

import v1 "github.com/garethgeorge/backrest/gen/go/v1"
import (
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"go.uber.org/zap"
)

var migrations = []func(*v1.Config){
migration001PrunePolicy,
Expand All @@ -14,6 +17,7 @@ func ApplyMigrations(config *v1.Config) error {
startMigration = 0
}
for idx := startMigration; idx < len(migrations); idx += 1 {
zap.S().Infof("applying config migration %d", idx+1)
migrations[idx](config)
}
config.Version = CurrentVersion
Expand Down
8 changes: 8 additions & 0 deletions internal/config/stringutil/stringutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package stringutil

import "regexp"

func SanitizeID(id string) string {
reg := regexp.MustCompile(`[^a-zA-Z0-9_\-\.]+`)
return reg.ReplaceAllString(id, "_")
}
46 changes: 46 additions & 0 deletions internal/config/stringutil/stringutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package stringutil

import "testing"

func TestSanitizeID(t *testing.T) {
tcs := []struct {
name string
id string
want string
}{
{
name: "empty",
id: "",
want: "",
},
{
name: "no change",
id: "abc123",
want: "abc123",
},
{
name: "spaces",
id: "a b c 1 2 3",
want: "a_b_c_1_2_3",
},
{
name: "special characters",
id: "a!b@c#1$2%3",
want: "a_b_c_1_2_3",
},
{
name: "unicode",
id: "a👍b👍c👍1👍2👍3",
want: "a_b_c_1_2_3",
},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got := SanitizeID(tc.id)
if got != tc.want {
t.Errorf("SanitizeID(%q) = %q, want %q", tc.id, got, tc.want)
}
})
}
}
14 changes: 14 additions & 0 deletions internal/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ import (
"strings"

v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/internal/config/stringutil"
"github.com/gitploy-io/cronexpr"
"github.com/hashicorp/go-multierror"
)

func ValidateConfig(c *v1.Config) error {
var err error

c.Instance, err = validateID(c.Instance)
if err != nil {
err = multierror.Append(err, fmt.Errorf("instance ID: %w", err))
}

repos := make(map[string]*v1.Repo)
if c.Repos != nil {
for _, repo := range c.Repos {
Expand Down Expand Up @@ -109,3 +116,10 @@ func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error {

return err
}

func validateID(id string) (string, error) {
if len(id) > 32 {
return "", fmt.Errorf("id %q is too long", id)
}
return stringutil.SanitizeID(id), nil
}
52 changes: 21 additions & 31 deletions internal/orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"time"

v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/internal/config"
"github.com/garethgeorge/backrest/internal/hook"
"github.com/garethgeorge/backrest/internal/ioutil"
"github.com/garethgeorge/backrest/internal/oplog"
Expand Down Expand Up @@ -67,7 +66,7 @@ func NewOrchestrator(resticBin string, cfg *v1.Config, oplog *oplog.OpLog, logSt
OpLog: oplog,
config: cfg,
// repoPool created with a memory store to ensure the config is updated in an atomic operation with the repo pool's config value.
repoPool: newResticRepoPool(resticBin, &config.MemoryStore{Config: cfg}),
repoPool: newResticRepoPool(resticBin, cfg),
taskQueue: queue.NewTimePriorityQueue[stContainer](),
hookExecutor: hook.NewHookExecutor(oplog, logStore),
logStore: logStore,
Expand Down Expand Up @@ -121,13 +120,8 @@ func (o *Orchestrator) curTime() time.Time {

func (o *Orchestrator) ApplyConfig(cfg *v1.Config) error {
o.mu.Lock()
o.config = cfg

// Update the config provided to the repo pool which is cached and diffed separately.
if err := o.repoPool.configProvider.Update(cfg); err != nil {
o.mu.Unlock()
return fmt.Errorf("failed to update repo pool config: %w", err)
}
o.config = proto.Clone(cfg).(*v1.Config)
o.repoPool = newResticRepoPool(o.repoPool.resticPath, o.config)
o.mu.Unlock()
return o.ScheduleDefaultTasks(cfg)
}
Expand Down Expand Up @@ -404,55 +398,51 @@ func (o *Orchestrator) scheduleTaskHelper(t tasks.Task, priority int, curTime ti
return nil
}

func (o *Orchestrator) Config() *v1.Config {
o.mu.Lock()
defer o.mu.Unlock()
return proto.Clone(o.config).(*v1.Config)
}

// resticRepoPool caches restic repos.
type resticRepoPool struct {
mu sync.Mutex
resticPath string
repos map[string]*repo.RepoOrchestrator
configProvider config.ConfigStore
mu sync.Mutex
resticPath string
repos map[string]*repo.RepoOrchestrator
config *v1.Config
}

func newResticRepoPool(resticPath string, configProvider config.ConfigStore) *resticRepoPool {
func newResticRepoPool(resticPath string, config *v1.Config) *resticRepoPool {
return &resticRepoPool{
resticPath: resticPath,
repos: make(map[string]*repo.RepoOrchestrator),
configProvider: configProvider,
resticPath: resticPath,
repos: make(map[string]*repo.RepoOrchestrator),
config: config,
}
}

func (rp *resticRepoPool) GetRepo(repoId string) (*repo.RepoOrchestrator, error) {
cfg, err := rp.configProvider.Get()
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
}

rp.mu.Lock()
defer rp.mu.Unlock()

if cfg.Repos == nil {
if rp.config.Repos == nil {
return nil, ErrRepoNotFound
}

var repoProto *v1.Repo
for _, r := range cfg.Repos {
for _, r := range rp.config.Repos {
if r.GetId() == repoId {
repoProto = r
}
}

if repoProto == nil {
return nil, ErrRepoNotFound
}

// Check if we already have a repo for this id, if we do return it.
r, ok := rp.repos[repoId]
if ok && proto.Equal(r.Config(), repoProto) {
if ok {
return r, nil
}
delete(rp.repos, repoId)

// Otherwise create a new repo.
r, err = repo.NewRepoOrchestrator(repoProto, rp.resticPath)
r, err := repo.NewRepoOrchestrator(rp.config, repoProto, rp.resticPath)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 4847010

Please sign in to comment.