Skip to content

Commit 0999ae9

Browse files
committed
config: unify Get{Source,Store,Destination} behaviour
* let all of them to resolve internally the "at aliases" syntax * all of them return a map or an error, not a boolean * they verify internally that location is set The visible changes are: * the message for an alias not found or a location missing is slightly different * passphrase_cmd gets replaced everywhere: I've checked all the currently packaged importers and exporters, and no-one is using it, I'm fine with this trade-off for now, in the near future it'll get replaced by a better mechanism anyway.
1 parent cea2916 commit 0999ae9

12 files changed

Lines changed: 197 additions & 168 deletions

File tree

config/config.go

Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
package config
22

33
import (
4+
"errors"
45
"fmt"
56
"net/url"
67
"path"
78
"path/filepath"
89
"strings"
10+
)
911

10-
"maps"
12+
var (
13+
ErrNoLocation = errors.New("location is missing")
14+
ErrNotFound = errors.New("configuration not found")
1115
)
1216

1317
type Config struct {
1418
DefaultRepository string
15-
Repositories map[string]RepositoryConfig
16-
Sources map[string]SourceConfig
17-
Destinations map[string]DestinationConfig
19+
Repositories map[string]map[string]string
20+
Sources map[string]map[string]string
21+
Destinations map[string]map[string]string
1822
}
1923

20-
type RepositoryConfig = map[string]string
21-
type SourceConfig = map[string]string
22-
type DestinationConfig = map[string]string
23-
2424
func NewConfig() *Config {
2525
return &Config{
26-
Repositories: make(map[string]RepositoryConfig),
27-
Sources: make(map[string]SourceConfig),
28-
Destinations: make(map[string]DestinationConfig),
26+
Repositories: make(map[string]map[string]string),
27+
Sources: make(map[string]map[string]string),
28+
Destinations: make(map[string]map[string]string),
2929
}
3030
}
3131

@@ -34,76 +34,45 @@ func (c *Config) HasRepository(name string) bool {
3434
return ok
3535
}
3636

37-
func (c *Config) GetRepository(name string) (map[string]string, error) {
37+
func (c *Config) get(name, kind string, configs map[string]map[string]string) (map[string]string, error) {
3838
if !strings.HasPrefix(name, "@") {
3939
return map[string]string{"location": name}, nil
4040
}
4141

42-
name, rootOverride := resolveRootOverride(name)
42+
name, rootOverride := resolveRootOverride(name[1:])
4343

44-
kv, ok := c.Repositories[name[1:]]
44+
kv, ok := configs[name]
4545
if !ok {
46-
return nil, fmt.Errorf("could not resolve repository: %s", name)
46+
return nil, fmt.Errorf("%s %w: %q", kind, ErrNotFound, name)
4747
}
48-
if _, ok := kv["location"]; !ok {
49-
return nil, fmt.Errorf("repository %s has no location", name)
50-
} else {
51-
res := make(map[string]string)
52-
maps.Copy(res, kv)
5348

54-
location, err := applyRootOverride(res["location"], rootOverride)
55-
if err != nil {
56-
return nil, err
57-
}
58-
res["location"] = location
59-
return res, nil
49+
if _, ok := kv["location"]; !ok {
50+
return nil, fmt.Errorf("%s %w", kind, ErrNoLocation)
6051
}
52+
53+
return resolve(kv, rootOverride)
54+
}
55+
56+
func (c *Config) GetRepository(name string) (map[string]string, error) {
57+
return c.get(name, "repository", c.Repositories)
6158
}
6259

6360
func (c *Config) HasSource(name string) bool {
6461
_, ok := c.Sources[name]
6562
return ok
6663
}
6764

68-
func (c *Config) GetSource(name string) (map[string]string, bool) {
69-
name, rootOverride := resolveRootOverride(name)
70-
71-
if kv, ok := c.Sources[name]; !ok {
72-
return nil, false
73-
} else {
74-
res := make(map[string]string)
75-
maps.Copy(res, kv)
76-
77-
location, err := applyRootOverride(res["location"], rootOverride)
78-
if err != nil {
79-
return nil, false
80-
}
81-
res["location"] = location
82-
return res, ok
83-
}
65+
func (c *Config) GetSource(name string) (map[string]string, error) {
66+
return c.get(name, "source", c.Sources)
8467
}
8568

8669
func (c *Config) HasDestination(name string) bool {
8770
_, ok := c.Destinations[name]
8871
return ok
8972
}
9073

91-
func (c *Config) GetDestination(name string) (map[string]string, bool) {
92-
name, rootOverride := resolveRootOverride(name)
93-
94-
if kv, ok := c.Destinations[name]; !ok {
95-
return nil, false
96-
} else {
97-
res := make(map[string]string)
98-
maps.Copy(res, kv)
99-
100-
location, err := applyRootOverride(res["location"], rootOverride)
101-
if err != nil {
102-
return nil, false
103-
}
104-
res["location"] = location
105-
return res, ok
106-
}
74+
func (c *Config) GetDestination(name string) (map[string]string, error) {
75+
return c.get(name, "destination", c.Destinations)
10776
}
10877

10978
func resolveRootOverride(name string) (string, string) {

config/config_extra_test.go

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,23 @@ func TestHasDestination(t *testing.T) {
2121
c := NewConfig()
2222
require.False(t, c.HasDestination("missing"))
2323

24-
c.Destinations["dst"] = DestinationConfig{"location": "/tmp/out"}
24+
c.Destinations["dst"] = map[string]string{"location": "/tmp/out"}
2525
require.True(t, c.HasDestination("dst"))
2626
}
2727

2828
func TestGetDestination(t *testing.T) {
2929
c := NewConfig()
3030

3131
// missing
32-
got, ok := c.GetDestination("missing")
33-
require.False(t, ok)
32+
got, err := c.GetDestination("@missing")
33+
require.Error(t, err)
34+
require.ErrorIs(t, err, ErrNotFound)
3435
require.Nil(t, got)
3536

3637
// present
37-
c.Destinations["dst"] = DestinationConfig{"location": "/tmp/out", "extra": "yes"}
38-
got, ok = c.GetDestination("dst")
39-
require.True(t, ok)
38+
c.Destinations["dst"] = map[string]string{"location": "/tmp/out", "extra": "yes"}
39+
got, err = c.GetDestination("@dst")
40+
require.NoError(t, err)
4041
require.Equal(t, "/tmp/out", got["location"])
4142
require.Equal(t, "yes", got["extra"])
4243

@@ -54,9 +55,9 @@ func TestResolveRootOverride(t *testing.T) {
5455
{"plain", "plain", ""},
5556
{"name:/abs/path", "name", "/abs/path"},
5657
{"name:rel/path", "name", "rel/path"},
57-
{"name:", "name", ""}, // trailing colon -> empty override
58-
{":/abs", "", "/abs"}, // leading colon -> empty name
59-
{"a:b:c", "a", "b:c"}, // only the first colon splits
58+
{"name:", "name", ""}, // trailing colon -> empty override
59+
{":/abs", "", "/abs"}, // leading colon -> empty name
60+
{"a:b:c", "a", "b:c"}, // only the first colon splits
6061
}
6162
for _, c := range cases {
6263
t.Run(c.input, func(t *testing.T) {
@@ -110,7 +111,7 @@ func TestApplyRootOverride_URL_BadLocationReturnsError(t *testing.T) {
110111

111112
func TestGetRepository_RootOverrideAppliedToLocalLocation(t *testing.T) {
112113
c := NewConfig()
113-
c.Repositories["repo"] = RepositoryConfig{"location": "/var/backups"}
114+
c.Repositories["repo"] = map[string]string{"location": "/var/backups"}
114115

115116
got, err := c.GetRepository("@repo:/elsewhere")
116117
require.NoError(t, err)
@@ -123,7 +124,7 @@ func TestGetRepository_RootOverrideAppliedToLocalLocation(t *testing.T) {
123124

124125
func TestGetRepository_RootOverrideAppliedToURL(t *testing.T) {
125126
c := NewConfig()
126-
c.Repositories["repo"] = RepositoryConfig{"location": "s3://bucket/base"}
127+
c.Repositories["repo"] = map[string]string{"location": "s3://bucket/base"}
127128

128129
got, err := c.GetRepository("@repo:sub")
129130
require.NoError(t, err)
@@ -132,7 +133,7 @@ func TestGetRepository_RootOverrideAppliedToURL(t *testing.T) {
132133

133134
func TestGetRepository_RootOverridePropagatesURLParseError(t *testing.T) {
134135
c := NewConfig()
135-
c.Repositories["repo"] = RepositoryConfig{"location": "ht\x00tp://x"}
136+
c.Repositories["repo"] = map[string]string{"location": "ht\x00tp://x"}
136137

137138
_, err := c.GetRepository("@repo:/x")
138139
require.Error(t, err)
@@ -149,44 +150,44 @@ func TestGetRepository_DirectPathPassesThrough(t *testing.T) {
149150

150151
func TestGetSource_RootOverrideAppliedToLocalLocation(t *testing.T) {
151152
c := NewConfig()
152-
c.Sources["src"] = SourceConfig{"location": "/data"}
153+
c.Sources["src"] = map[string]string{"location": "/data"}
153154

154-
got, ok := c.GetSource("src:/elsewhere")
155-
require.True(t, ok)
155+
got, err := c.GetSource("@src:/elsewhere")
156+
require.NoError(t, err)
156157
require.Equal(t, "/elsewhere", got["location"])
157158
}
158159

159160
func TestGetSource_RootOverrideReturnsFalseOnURLParseError(t *testing.T) {
160161
c := NewConfig()
161162
// GetSource swallows the applyRootOverride error and returns ok=false.
162-
c.Sources["src"] = SourceConfig{"location": "ht\x00tp://x"}
163+
c.Sources["src"] = map[string]string{"location": "ht\x00tp://x"}
163164

164-
_, ok := c.GetSource("src:/x")
165-
require.False(t, ok)
165+
_, err := c.GetSource("@src:/x")
166+
require.Error(t, err)
166167
}
167168

168169
func TestGetDestination_RootOverrideAppliedToLocalLocation(t *testing.T) {
169170
c := NewConfig()
170-
c.Destinations["dst"] = DestinationConfig{"location": "/out"}
171+
c.Destinations["dst"] = map[string]string{"location": "/out"}
171172

172-
got, ok := c.GetDestination("dst:sub")
173-
require.True(t, ok)
173+
got, err := c.GetDestination("@dst:sub")
174+
require.NoError(t, err)
174175
require.Equal(t, "/out/sub", got["location"])
175176
}
176177

177178
func TestGetDestination_RootOverrideReturnsFalseOnURLParseError(t *testing.T) {
178179
c := NewConfig()
179-
c.Destinations["dst"] = DestinationConfig{"location": "ht\x00tp://x"}
180+
c.Destinations["dst"] = map[string]string{"location": "ht\x00tp://x"}
180181

181-
_, ok := c.GetDestination("dst:/x")
182-
require.False(t, ok)
182+
_, err := c.GetDestination("@dst:/x")
183+
require.Error(t, err)
183184
}
184185

185186
func TestGetRepository_ResolveRootOverrideStripsAtPrefix(t *testing.T) {
186187
// Sanity: the "@" prefix is on the *full* token, then the colon split
187188
// happens on what's left. So "@name:override" -> name lookup is "name".
188189
c := NewConfig()
189-
c.Repositories["repo"] = RepositoryConfig{"location": "/x"}
190+
c.Repositories["repo"] = map[string]string{"location": "/x"}
190191

191192
got, err := c.GetRepository("@repo:sub")
192193
require.NoError(t, err)

config/config_test.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@ import (
88

99
func TestHasRepository(t *testing.T) {
1010
cfg := &Config{
11-
Repositories: make(map[string]RepositoryConfig),
11+
Repositories: make(map[string]map[string]string),
1212
}
1313

1414
// Test non-existent repository
1515
require.False(t, cfg.HasRepository("test-repo"))
1616

1717
// Test existing repository
18-
cfg.Repositories["test-repo"] = RepositoryConfig{"location": "/test/path"}
18+
cfg.Repositories["test-repo"] = map[string]string{"location": "/test/path"}
1919
require.True(t, cfg.HasRepository("test-repo"))
2020
}
2121

2222
func TestGetRepository(t *testing.T) {
2323
cfg := &Config{
24-
Repositories: make(map[string]RepositoryConfig),
24+
Repositories: make(map[string]map[string]string),
2525
}
2626

2727
// Test direct path
@@ -34,43 +34,62 @@ func TestGetRepository(t *testing.T) {
3434
require.Error(t, err)
3535

3636
// Test repository without location
37-
cfg.Repositories["test-repo"] = RepositoryConfig{"other": "value"}
37+
cfg.Repositories["test-repo"] = map[string]string{"other": "value"}
3838
_, err = cfg.GetRepository("@test-repo")
3939
require.Error(t, err)
4040

4141
// Test valid repository
42-
cfg.Repositories["test-repo"] = RepositoryConfig{"location": "/test/path"}
42+
cfg.Repositories["test-repo"] = map[string]string{"location": "/test/path"}
4343
repo, err = cfg.GetRepository("@test-repo")
4444
require.NoError(t, err)
4545
require.Equal(t, "/test/path", repo["location"])
4646
}
4747

4848
func TestHasSource(t *testing.T) {
4949
cfg := &Config{
50-
Sources: make(map[string]SourceConfig),
50+
Sources: make(map[string]map[string]string),
5151
}
5252

5353
// Test non-existent source
5454
require.False(t, cfg.HasSource("test-source"))
5555

5656
// Test existing source
57-
cfg.Sources["test-source"] = SourceConfig{"url": "test://url"}
57+
cfg.Sources["test-source"] = map[string]string{"url": "test://url"}
5858
require.True(t, cfg.HasSource("test-source"))
5959
}
6060

6161
func TestGetSource(t *testing.T) {
6262
cfg := &Config{
63-
Sources: make(map[string]SourceConfig),
63+
Sources: make(map[string]map[string]string),
6464
}
6565

6666
// Test non-existent source
67-
source, ok := cfg.GetSource("test-source")
68-
require.False(t, ok)
67+
source, err := cfg.GetSource("@test-source")
68+
require.Error(t, err)
69+
require.ErrorIs(t, err, ErrNotFound)
6970
require.Nil(t, source)
7071

7172
// Test existing source
72-
cfg.Sources["test-source"] = SourceConfig{"url": "test://url"}
73-
source, ok = cfg.GetSource("test-source")
74-
require.True(t, ok)
73+
cfg.Sources["test-source"] = map[string]string{
74+
"url": "test://url",
75+
"location": "xxx",
76+
}
77+
source, err = cfg.GetSource("@test-source")
78+
require.NoError(t, err)
7579
require.Equal(t, "test://url", source["url"])
7680
}
81+
82+
func TestGetNoLocation(t *testing.T) {
83+
cfg := Config{
84+
Sources: map[string]map[string]string{
85+
"foo": {
86+
"option_a": "true",
87+
},
88+
},
89+
}
90+
91+
opts, err := cfg.get("@foo", "source", cfg.Sources)
92+
require.Error(t, err)
93+
require.ErrorIs(t, err, ErrNoLocation)
94+
require.Nil(t, opts)
95+
}

config/old.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
)
1010

1111
type OldConfig struct {
12-
DefaultRepository string `yaml:"default-repo"`
13-
Repositories map[string]RepositoryConfig `yaml:"repositories"`
14-
Remotes map[string]SourceConfig `yaml:"remotes"`
12+
DefaultRepository string `yaml:"default-repo"`
13+
Repositories map[string]map[string]string `yaml:"repositories"`
14+
Remotes map[string]map[string]string `yaml:"remotes"`
1515
}
1616

1717
func LoadOldConfigIfExists(configFile string) (*Config, error) {
@@ -34,7 +34,7 @@ func LoadOldConfigIfExists(configFile string) (*Config, error) {
3434
cfg.DefaultRepository = old.DefaultRepository
3535
cfg.Repositories = old.Repositories
3636
cfg.Sources = old.Remotes
37-
cfg.Destinations = make(map[string]DestinationConfig)
37+
cfg.Destinations = make(map[string]map[string]string)
3838
for key, val := range cfg.Sources {
3939
res := make(map[string]string)
4040
maps.Copy(res, val)

0 commit comments

Comments
 (0)