From 6895df1c8330c4efe26e7a044ad7e9ad356b7481 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Mon, 4 Nov 2024 09:20:42 -0500 Subject: [PATCH] fix(config): fix config marshaling producing unreadable config file (#402) There are two practical changes when the config gets updated: - The `forgetoption` and `configoption` bug is now gone - Superfluous config keys no longer get written out --- internal/backend.go | 18 ++++++++-------- internal/config.go | 10 ++++----- internal/config_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ internal/location.go | 30 +++++++++++++------------- 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/internal/backend.go b/internal/backend.go index 669935a..26eadac 100644 --- a/internal/backend.go +++ b/internal/backend.go @@ -14,19 +14,19 @@ import ( ) type BackendRest struct { - User string `mapstructure:"user,omitempty"` - Password string `mapstructure:"password,omitempty"` + User string `mapstructure:"user,omitempty" yaml:"user,omitempty"` + Password string `mapstructure:"password,omitempty" yaml:"password,omitempty"` } type Backend struct { name string - Type string `mapstructure:"type,omitempty"` - Path string `mapstructure:"path,omitempty"` - Key string `mapstructure:"key,omitempty"` - RequireKey bool `mapstructure:"requireKey,omitempty"` - Env map[string]string `mapstructure:"env,omitempty"` - Rest BackendRest `mapstructure:"rest,omitempty"` - Options Options `mapstructure:"options,omitempty"` + Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"` + Path string `mapstructure:"path,omitempty" yaml:"path,omitempty"` + Key string `mapstructure:"key,omitempty" yaml:"key,omitempty"` + RequireKey bool `mapstructure:"requireKey,omitempty" yaml:"requireKey,omitempty"` + Env map[string]string `mapstructure:"env,omitempty" yaml:"env,omitempty"` + Rest BackendRest `mapstructure:"rest,omitempty" yaml:"rest,omitempty"` + Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"` } func GetBackend(name string) (Backend, bool) { diff --git a/internal/config.go b/internal/config.go index 24d948c..c5430d3 100644 --- a/internal/config.go +++ b/internal/config.go @@ -23,11 +23,11 @@ type OptionMap map[string][]interface{} type Options map[string]OptionMap type Config struct { - Version string `mapstructure:"version"` - Extras interface{} `mapstructure:"extras"` - Locations map[string]Location `mapstructure:"locations"` - Backends map[string]Backend `mapstructure:"backends"` - Global Options `mapstructure:"global"` + Version string `mapstructure:"version" yaml:"version"` + Extras interface{} `mapstructure:"extras" yaml:"extras"` + Locations map[string]Location `mapstructure:"locations" yaml:"locations"` + Backends map[string]Backend `mapstructure:"backends" yaml:"backends"` + Global Options `mapstructure:"global" yaml:"global"` } var once sync.Once diff --git a/internal/config_test.go b/internal/config_test.go index 0d55f8e..99185e8 100644 --- a/internal/config_test.go +++ b/internal/config_test.go @@ -1,10 +1,15 @@ package internal import ( + "path" "reflect" "strconv" "strings" + "sync" "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" ) func TestOptionToString(t *testing.T) { @@ -143,6 +148,48 @@ func TestGetOptionsMultipleKeys(t *testing.T) { reflect.DeepEqual(result, expected) } +func TestSaveConfigProducesReadableConfig(t *testing.T) { + workDir := t.TempDir() + viper.SetConfigFile(path.Join(workDir, ".autorestic.yml")) + + // Required to appease the config reader + viper.Set("version", 2) + + c := Config{ + Version: "2", + Locations: map[string]Location{ + "test": { + Type: "local", + name: "test", + From: []string{"in-dir"}, + To: []string{"test"}, + // ForgetOption & ConfigOption have previously marshalled in a way that + // can't get read correctly + ForgetOption: "foo", + CopyOption: map[string][]string{"foo": {"bar"}}, + }, + }, + Backends: map[string]Backend{ + "test": { + name: "test", + Type: "local", + Path: "backup-target", + Key: "supersecret", + }, + }, + } + + err := c.SaveConfig() + assert.NoError(t, err) + + // Ensure we the config reading logic actually runs + config = nil + once = sync.Once{} + readConfig := GetConfig() + assert.NotNil(t, readConfig) + assert.Equal(t, c, *readConfig) +} + func assertEqual[T comparable](t testing.TB, result, expected T) { t.Helper() diff --git a/internal/location.go b/internal/location.go index ce970ec..221c5f3 100644 --- a/internal/location.go +++ b/internal/location.go @@ -34,26 +34,26 @@ const ( ) type Hooks struct { - Dir string `mapstructure:"dir"` - PreValidate HookArray `mapstructure:"prevalidate,omitempty"` - Before HookArray `mapstructure:"before,omitempty"` - After HookArray `mapstructure:"after,omitempty"` - Success HookArray `mapstructure:"success,omitempty"` - Failure HookArray `mapstructure:"failure,omitempty"` + Dir string `mapstructure:"dir" yaml:"dir"` + PreValidate HookArray `mapstructure:"prevalidate,omitempty" yaml:"prevalidate,omitempty"` + Before HookArray `mapstructure:"before,omitempty" yaml:"before,omitempty"` + After HookArray `mapstructure:"after,omitempty" yaml:"after,omitempty"` + Success HookArray `mapstructure:"success,omitempty" yaml:"success,omitempty"` + Failure HookArray `mapstructure:"failure,omitempty" yaml:"failure,omitempty"` } type LocationCopy = map[string][]string type Location struct { - name string `mapstructure:",omitempty"` - From []string `mapstructure:"from,omitempty"` - Type string `mapstructure:"type,omitempty"` - To []string `mapstructure:"to,omitempty"` - Hooks Hooks `mapstructure:"hooks,omitempty"` - Cron string `mapstructure:"cron,omitempty"` - Options Options `mapstructure:"options,omitempty"` - ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` - CopyOption LocationCopy `mapstructure:"copy,omitempty"` + name string `mapstructure:",omitempty" yaml:",omitempty"` + From []string `mapstructure:"from,omitempty" yaml:"from,omitempty"` + Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"` + To []string `mapstructure:"to,omitempty" yaml:"to,omitempty"` + Hooks Hooks `mapstructure:"hooks,omitempty" yaml:"hooks,omitempty"` + Cron string `mapstructure:"cron,omitempty" yaml:"cron,omitempty"` + Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"` + ForgetOption LocationForgetOption `mapstructure:"forget,omitempty" yaml:"forget,omitempty"` + CopyOption LocationCopy `mapstructure:"copy,omitempty" yaml:"copy,omitempty"` } func GetLocation(name string) (Location, bool) {