Skip to content

Commit 00564f6

Browse files
authored
Add Configurable Storage Directory Support (#313)
* feat: add configurable storage directory Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: handle bare tilde in path expansion Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: handle errcheck violations in paths and paths_test Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: remove duplicate ConfigManager initialization from main() Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: resolve relative paths to absolute in ResolvePathConfig Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: use cm.With() for ZotConfigRaw to respect ConfigManager mutex Signed-off-by: bupd <bupdprasanth@gmail.com> * style: replace interface{} with any Signed-off-by: bupd <bupdprasanth@gmail.com> * chore: remove dead DefaultConfigPath and DefaultPrevConfigPath constants Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: add missing newline in config manager error message Signed-off-by: bupd <bupdprasanth@gmail.com> * test: use t.TempDir(), add edge cases, validate JSON output Signed-off-by: bupd <bupdprasanth@gmail.com> * style: replace interface{} with any across root module Signed-off-by: bupd <bupdprasanth@gmail.com> * docs: add Go style rules to CLAUDE.md Signed-off-by: bupd <bupdprasanth@gmail.com> * style: replace interface{} with any in ground-control module Signed-off-by: bupd <bupdprasanth@gmail.com> --------- Signed-off-by: bupd <bupdprasanth@gmail.com>
1 parent e5d5cc9 commit 00564f6

File tree

18 files changed

+397
-71
lines changed

18 files changed

+397
-71
lines changed

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ Ground Control uses environment variables (see ground-control/.env.example):
125125
- Database connection (DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD)
126126
- Server settings (PORT, APP_ENV)
127127

128+
## Go Style Rules
129+
130+
- Use `any` instead of `interface{}`. The project targets Go 1.22+ where `any` is the preferred alias.
131+
- Use `t.TempDir()` in tests instead of manual temp paths with `os.TempDir()`.
132+
- Use `cm.With()` modifiers for all ConfigManager mutations (never mutate via `cm.GetConfig()` directly).
133+
128134
## Important Development Notes
129135

130136
### State Management

cmd/main.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ type SatelliteOptions struct {
4545
SPIFFEExpectedServerID string
4646
BYORegistry bool
4747
RegistryURL string
48-
RegistryUsername string
48+
RegistryUsername string
4949
RegistryPassword string
50+
ConfigDir string
5051
}
5152

5253
func main() {
@@ -64,6 +65,7 @@ func main() {
6465
flag.StringVar(&opts.RegistryURL, "registry-url", "", "External registry URL")
6566
flag.StringVar(&opts.RegistryUsername, "registry-username", "", "External registry username")
6667
flag.StringVar(&opts.RegistryPassword, "registry-password", "", "External registry password")
68+
flag.StringVar(&opts.ConfigDir, "config-dir", "", "Configuration directory path (default: ~/.config/satellite)")
6769

6870
flag.Parse()
6971

@@ -97,6 +99,25 @@ func main() {
9799
if opts.RegistryPassword == "" {
98100
opts.RegistryPassword = os.Getenv("REGISTRY_PASSWORD")
99101
}
102+
if opts.ConfigDir == "" {
103+
opts.ConfigDir = os.Getenv("CONFIG_DIR")
104+
}
105+
106+
// Resolve config directory path
107+
if opts.ConfigDir == "" {
108+
var err error
109+
opts.ConfigDir, err = config.DefaultConfigDir()
110+
if err != nil {
111+
fmt.Printf("Error resolving default config directory: %v\n", err)
112+
os.Exit(1)
113+
}
114+
}
115+
116+
pathConfig, err := config.ResolvePathConfig(opts.ConfigDir)
117+
if err != nil {
118+
fmt.Printf("Error resolving config paths: %v\n", err)
119+
os.Exit(1)
120+
}
100121

101122
// Token is not required if SPIFFE is enabled
102123
if !opts.SPIFFEEnabled && (opts.Token == "" || opts.GroundControlURL == "") {
@@ -112,21 +133,21 @@ func main() {
112133
os.Exit(1)
113134
}
114135

115-
err := run(opts)
136+
err = run(opts, pathConfig)
116137
if err != nil {
117138
fmt.Printf("fatal: %v\n", err)
118139
os.Exit(1)
119140
}
120141
}
121142

122-
func run(opts SatelliteOptions) error {
143+
func run(opts SatelliteOptions, pathConfig *config.PathConfig) error {
123144
ctx, cancel := utils.SetupContext(context.Background())
124145
defer cancel()
125146
wg, ctx := errgroup.WithContext(ctx)
126147

127-
cm, warnings, err := config.InitConfigManager(opts.Token, opts.GroundControlURL, config.DefaultConfigPath, config.DefaultPrevConfigPath, opts.JSONLogging, opts.UseUnsecure)
148+
cm, warnings, err := config.InitConfigManager(opts.Token, opts.GroundControlURL, pathConfig.ConfigFile, pathConfig.PrevConfigFile, opts.JSONLogging, opts.UseUnsecure)
128149
if err != nil {
129-
fmt.Printf("Error initiating the config manager: %v", err)
150+
fmt.Printf("Error initiating the config manager: %v\n", err)
130151
return err
131152
}
132153

@@ -149,6 +170,13 @@ func run(opts SatelliteOptions) error {
149170
)
150171
}
151172

173+
// Update Zot config with storage path
174+
zotConfigJSON, err := config.BuildZotConfigWithStoragePath(pathConfig.ZotStorageDir)
175+
if err != nil {
176+
return fmt.Errorf("build Zot config: %w", err)
177+
}
178+
cm.With(config.SetZotConfigRaw(json.RawMessage(zotConfigJSON)))
179+
152180
// Resolve local registry endpoint for CRI mirror config
153181
localRegistryEndpoint, err := resolveLocalRegistryEndpoint(cm)
154182
if err != nil {
@@ -170,17 +198,18 @@ func run(opts SatelliteOptions) error {
170198
ctx,
171199
cm,
172200
log,
201+
pathConfig.ZotTempConfig,
173202
nil, // Will be set after scheduler creation
174203
)
175204

176205
eventChan := make(chan struct{})
177206

178207
// Handle registry setup
179-
wg.Go(func() error { return handleRegistrySetup(ctx, log, cm) })
208+
wg.Go(func() error { return handleRegistrySetup(ctx, log, cm, pathConfig) })
180209

181210
// Watch for changes in the config file
182211
wg.Go(func() error {
183-
return watcher.WatchChanges(ctx, log.With().Str("component", "file watcher").Logger(), config.DefaultConfigPath, eventChan)
212+
return watcher.WatchChanges(ctx, log.With().Str("component", "file watcher").Logger(), pathConfig.ConfigFile, eventChan)
184213
})
185214

186215
// Watch for changes in the config file
@@ -233,11 +262,11 @@ func resolveLocalRegistryEndpoint(cm *config.ConfigManager) (string, error) {
233262
if cm.GetOwnRegistry() {
234263
return utils.FormatRegistryURL(cm.GetLocalRegistryURL()), nil
235264
}
236-
var data map[string]interface{}
265+
var data map[string]any
237266
if err := json.Unmarshal(cm.GetRawZotConfig(), &data); err != nil {
238267
return "", fmt.Errorf("unmarshalling zot config: %w", err)
239268
}
240-
httpData, ok := data["http"].(map[string]interface{})
269+
httpData, ok := data["http"].(map[string]any)
241270
if !ok {
242271
return "", fmt.Errorf("missing 'http' section in zot config")
243272
}
@@ -249,7 +278,7 @@ func resolveLocalRegistryEndpoint(cm *config.ConfigManager) (string, error) {
249278
return addr + ":" + port, nil
250279
}
251280

252-
func handleRegistrySetup(ctx context.Context, log *zerolog.Logger, cm *config.ConfigManager) error {
281+
func handleRegistrySetup(ctx context.Context, log *zerolog.Logger, cm *config.ConfigManager, pathConfig *config.PathConfig) error {
253282
log.Debug().Msg("Setting up local registry")
254283

255284
if cm.GetOwnRegistry() {
@@ -264,7 +293,7 @@ func handleRegistrySetup(ctx context.Context, log *zerolog.Logger, cm *config.Co
264293

265294
log.Info().Msg("Launching default registry")
266295

267-
zm := registry.NewZotManager(log.With().Str("component", "zot manager").Logger(), cm.GetRawZotConfig())
296+
zm := registry.NewZotManager(log.With().Str("component", "zot manager").Logger(), cm.GetRawZotConfig(), pathConfig.ZotTempConfig)
268297

269298
if err := zm.HandleRegistrySetup(ctx); err != nil {
270299
return fmt.Errorf("default registry setup failed: %w", err)

ground-control/internal/server/handleResponse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func WriteJSONError(w http.ResponseWriter, message string, statusCode int) {
3535
}
3636

3737
// write JSON response with given status code and data.
38-
func WriteJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) {
38+
func WriteJSONResponse(w http.ResponseWriter, statusCode int, data any) {
3939
respBytes, err := json.Marshal(data)
4040
if err != nil {
4141
log.Printf("Failed to marshal JSON response: %v", err)

ground-control/internal/server/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func getGroupStates(ctx context.Context, groups []database.SatelliteGroup, q *da
200200
return states, nil
201201
}
202202

203-
func DecodeRequestBody(r *http.Request, v interface{}) error {
203+
func DecodeRequestBody(r *http.Request, v any) error {
204204
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
205205
return &AppError{
206206
Message: "Invalid request body",

ground-control/internal/server/spire_handlers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func TestRegisterSatelliteRequest_Validation(t *testing.T) {
112112
resp := w.Result()
113113
defer resp.Body.Close()
114114

115-
var respBody map[string]interface{}
115+
var respBody map[string]any
116116
err = json.NewDecoder(resp.Body).Decode(&respBody)
117117
require.NoError(t, err)
118118

@@ -148,7 +148,7 @@ func TestListSpireAgentsHandler_NoSpireClient(t *testing.T) {
148148

149149
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
150150

151-
var respBody map[string]interface{}
151+
var respBody map[string]any
152152
err := json.NewDecoder(resp.Body).Decode(&respBody)
153153
require.NoError(t, err)
154154
require.Contains(t, respBody["message"], "SPIRE server not configured")

internal/container_runtime/containerd.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ func configureContainerd(certDir string) error {
9999
}
100100

101101
// loadToml loads existing TOML into a flexible type
102-
func loadToml(path string) (map[string]interface{}, error) {
103-
cfg := make(map[string]interface{})
102+
func loadToml(path string) (map[string]any, error) {
103+
cfg := make(map[string]any)
104104
if _, err := os.Stat(path); err == nil {
105105
if _, err := toml.DecodeFile(path, &cfg); err != nil {
106106
return nil, fmt.Errorf("failed to parse %s: %w", path, err)
@@ -109,13 +109,13 @@ func loadToml(path string) (map[string]interface{}, error) {
109109
return cfg, nil
110110
}
111111

112-
func loadNestedMap(parent map[string]interface{}, key string) map[string]interface{} {
112+
func loadNestedMap(parent map[string]any, key string) map[string]any {
113113
if v, ok := parent[key]; ok {
114-
if m, ok := v.(map[string]interface{}); ok {
114+
if m, ok := v.(map[string]any); ok {
115115
return m
116116
}
117117
}
118-
newMap := make(map[string]interface{})
118+
newMap := make(map[string]any)
119119
parent[key] = newMap
120120
return newMap
121121
}

internal/container_runtime/containerd_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type Host struct {
99
type ContainerdHosts struct {
1010
Server string `toml:"server"`
1111
Host map[string]Host `toml:"host"`
12-
Unknown map[string]interface{} `toml:",remain"` // do not touch parts we do not deal with
12+
Unknown map[string]any `toml:",remain"` // do not touch parts we do not deal with
1313
}
1414

1515
type ContainerdConfig struct {

internal/container_runtime/crio_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package runtime
33
// RegistriesConf models fields of /etc/containers/registries.conf
44
type RegistriesConf struct {
55
Registries []Registry `mapstructure:"registry" toml:"registry"`
6-
Unknown map[string]interface{} `mapstructure:",remain"` // do not touch parts we do not deal with
6+
Unknown map[string]any `mapstructure:",remain"` // do not touch parts we do not deal with
77
}
88

99
// Registry models registry field in /etc/containers/registries.conf

internal/hotreload/hotreload.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"sync"
99

10-
"github.com/container-registry/harbor-satellite/internal/registry"
1110
"github.com/container-registry/harbor-satellite/internal/scheduler"
1211
"github.com/container-registry/harbor-satellite/pkg/config"
1312
"github.com/rs/zerolog"
@@ -19,6 +18,7 @@ type HotReloadManager struct {
1918
cm *config.ConfigManager
2019
log *zerolog.Logger
2120
ctx context.Context
21+
zotTempPath string
2222
stateReplicationScheduler *scheduler.Scheduler
2323
changeCallbacks map[config.ConfigChangeType][]config.ConfigChangeCallback
2424
callbackMu sync.RWMutex
@@ -28,12 +28,14 @@ func NewHotReloadManager(
2828
ctx context.Context,
2929
cm *config.ConfigManager,
3030
log *zerolog.Logger,
31+
zotTempPath string,
3132
stateReplicationScheduler *scheduler.Scheduler,
3233
) *HotReloadManager {
3334
manager := &HotReloadManager{
3435
cm: cm,
3536
log: log,
3637
ctx: ctx,
38+
zotTempPath: zotTempPath,
3739
stateReplicationScheduler: stateReplicationScheduler,
3840
changeCallbacks: make(map[config.ConfigChangeType][]config.ConfigChangeCallback),
3941
}
@@ -138,12 +140,12 @@ func (hrm *HotReloadManager) handleZotConfigChange(change config.ConfigChange) e
138140
}
139141

140142
// Zot verify the config using below function hence taking same the path
141-
if err := server.LoadConfiguration(&cfg, registry.ZotTempPath); err != nil {
143+
if err := server.LoadConfiguration(&cfg, hrm.zotTempPath); err != nil {
142144
hrm.log.Error().Interface("config", &cfg).Msg("invalid config file")
143145
return err
144146
}
145147

146-
err := os.WriteFile(registry.ZotTempPath, hrm.cm.GetRawZotConfig(), 0600)
148+
err := os.WriteFile(hrm.zotTempPath, hrm.cm.GetRawZotConfig(), 0600)
147149
if err != nil {
148150
return fmt.Errorf("unable to change zot configuration: %w", err)
149151
}

internal/logger/logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func NewLogger(logLevel string, isJson bool) *zerolog.Logger {
4242
output := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "2006-01-02 15:04:05"}
4343

4444
// Customize the output for each log level
45-
output.FormatLevel = func(i interface{}) string {
45+
output.FormatLevel = func(i any) string {
4646
var l string
4747
if ll, ok := i.(string); ok {
4848
switch ll {

0 commit comments

Comments
 (0)