fix(config): add Reload method to properly re-read config from disk
The /admin/reload endpoint was calling Load() without first calling ReadInConfig(), meaning the config file wasn't being re-read from disk. This caused the in-memory config to be out of sync with the file. - Add Reload() method to ConfigService interface - Implement Reload() in config service that calls ReadInConfig() then Load() - Update handleReload to use Reload() instead of Load() - Regenerate mocks
- commit
- 11f41de547882b5df231b01d297e324e0f0e202f
- author
- brice · · 2026-01-18 08:02 UTC
- parent
- edb08f9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +74 -0
M internal/boundaries/in/config.go +4 -0
| @@ -11,6 +11,10 @@ type ConfigService interface { | |||
| 11 | 11 | // Load loads the configuration from the configured source. | |
| 12 | 12 | Load(ctx context.Context) error | |
| 13 | 13 | ||
| 14 | + | // Reload re-reads the configuration file from disk and loads it into memory. | |
| 15 | + | // This is different from Load() which only loads from the cached viper values. | |
| 16 | + | Reload(ctx context.Context) error | |
| 17 | + | ||
| 14 | 18 | // GetRoutes returns all configured routes. | |
| 15 | 19 | GetRoutes(ctx context.Context) []domain.Route | |
| 16 | 20 | ||
| @@ -11,6 +11,10 @@ type ConfigService interface { | |||||
| 11 | // Load loads the configuration from the configured source. | 11 | // Load loads the configuration from the configured source. | ||
| 12 | Load(ctx context.Context) error | 12 | Load(ctx context.Context) error | ||
| 13 | 13 | ||||
| 14 | + | // Reload re-reads the configuration file from disk and loads it into memory. | |||
| 15 | + | // This is different from Load() which only loads from the cached viper values. | |||
| 16 | + | Reload(ctx context.Context) error | |||
| 17 | + | ||||
| 14 | // GetRoutes returns all configured routes. | 18 | // GetRoutes returns all configured routes. | ||
| 15 | GetRoutes(ctx context.Context) []domain.Route | 19 | GetRoutes(ctx context.Context) []domain.Route | ||
| 16 | 20 | ||||
M internal/boundaries/in/mocks/mock_config_service.go +51 -0
| @@ -805,6 +805,57 @@ func (_c *MockConfigService_Load_Call) RunAndReturn(run func(ctx context.Context | |||
| 805 | 805 | return _c | |
| 806 | 806 | } | |
| 807 | 807 | ||
| 808 | + | // Reload provides a mock function for the type MockConfigService | |
| 809 | + | func (_mock *MockConfigService) Reload(ctx context.Context) error { | |
| 810 | + | ret := _mock.Called(ctx) | |
| 811 | + | ||
| 812 | + | if len(ret) == 0 { | |
| 813 | + | panic("no return value specified for Reload") | |
| 814 | + | } | |
| 815 | + | ||
| 816 | + | var r0 error | |
| 817 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok { | |
| 818 | + | r0 = returnFunc(ctx) | |
| 819 | + | } else { | |
| 820 | + | r0 = ret.Error(0) | |
| 821 | + | } | |
| 822 | + | return r0 | |
| 823 | + | } | |
| 824 | + | ||
| 825 | + | // MockConfigService_Reload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reload' | |
| 826 | + | type MockConfigService_Reload_Call struct { | |
| 827 | + | *mock.Call | |
| 828 | + | } | |
| 829 | + | ||
| 830 | + | // Reload is a helper method to define mock.On call | |
| 831 | + | // - ctx context.Context | |
| 832 | + | func (_e *MockConfigService_Expecter) Reload(ctx interface{}) *MockConfigService_Reload_Call { | |
| 833 | + | return &MockConfigService_Reload_Call{Call: _e.mock.On("Reload", ctx)} | |
| 834 | + | } | |
| 835 | + | ||
| 836 | + | func (_c *MockConfigService_Reload_Call) Run(run func(ctx context.Context)) *MockConfigService_Reload_Call { | |
| 837 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 838 | + | var arg0 context.Context | |
| 839 | + | if args[0] != nil { | |
| 840 | + | arg0 = args[0].(context.Context) | |
| 841 | + | } | |
| 842 | + | run( | |
| 843 | + | arg0, | |
| 844 | + | ) | |
| 845 | + | }) | |
| 846 | + | return _c | |
| 847 | + | } | |
| 848 | + | ||
| 849 | + | func (_c *MockConfigService_Reload_Call) Return(err error) *MockConfigService_Reload_Call { | |
| 850 | + | _c.Call.Return(err) | |
| 851 | + | return _c | |
| 852 | + | } | |
| 853 | + | ||
| 854 | + | func (_c *MockConfigService_Reload_Call) RunAndReturn(run func(ctx context.Context) error) *MockConfigService_Reload_Call { | |
| 855 | + | _c.Call.Return(run) | |
| 856 | + | return _c | |
| 857 | + | } | |
| 858 | + | ||
| 808 | 859 | // RemoveAttachment provides a mock function for the type MockConfigService | |
| 809 | 860 | func (_mock *MockConfigService) RemoveAttachment(ctx context.Context, domainOrGroup string, image string) error { | |
| 810 | 861 | ret := _mock.Called(ctx, domainOrGroup, image) | |
| @@ -805,6 +805,57 @@ func (_c *MockConfigService_Load_Call) RunAndReturn(run func(ctx context.Context | |||||
| 805 | return _c | 805 | return _c | ||
| 806 | } | 806 | } | ||
| 807 | 807 | ||||
| 808 | + | // Reload provides a mock function for the type MockConfigService | |||
| 809 | + | func (_mock *MockConfigService) Reload(ctx context.Context) error { | |||
| 810 | + | ret := _mock.Called(ctx) | |||
| 811 | + | ||||
| 812 | + | if len(ret) == 0 { | |||
| 813 | + | panic("no return value specified for Reload") | |||
| 814 | + | } | |||
| 815 | + | ||||
| 816 | + | var r0 error | |||
| 817 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok { | |||
| 818 | + | r0 = returnFunc(ctx) | |||
| 819 | + | } else { | |||
| 820 | + | r0 = ret.Error(0) | |||
| 821 | + | } | |||
| 822 | + | return r0 | |||
| 823 | + | } | |||
| 824 | + | ||||
| 825 | + | // MockConfigService_Reload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reload' | |||
| 826 | + | type MockConfigService_Reload_Call struct { | |||
| 827 | + | *mock.Call | |||
| 828 | + | } | |||
| 829 | + | ||||
| 830 | + | // Reload is a helper method to define mock.On call | |||
| 831 | + | // - ctx context.Context | |||
| 832 | + | func (_e *MockConfigService_Expecter) Reload(ctx interface{}) *MockConfigService_Reload_Call { | |||
| 833 | + | return &MockConfigService_Reload_Call{Call: _e.mock.On("Reload", ctx)} | |||
| 834 | + | } | |||
| 835 | + | ||||
| 836 | + | func (_c *MockConfigService_Reload_Call) Run(run func(ctx context.Context)) *MockConfigService_Reload_Call { | |||
| 837 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 838 | + | var arg0 context.Context | |||
| 839 | + | if args[0] != nil { | |||
| 840 | + | arg0 = args[0].(context.Context) | |||
| 841 | + | } | |||
| 842 | + | run( | |||
| 843 | + | arg0, | |||
| 844 | + | ) | |||
| 845 | + | }) | |||
| 846 | + | return _c | |||
| 847 | + | } | |||
| 848 | + | ||||
| 849 | + | func (_c *MockConfigService_Reload_Call) Return(err error) *MockConfigService_Reload_Call { | |||
| 850 | + | _c.Call.Return(err) | |||
| 851 | + | return _c | |||
| 852 | + | } | |||
| 853 | + | ||||
| 854 | + | func (_c *MockConfigService_Reload_Call) RunAndReturn(run func(ctx context.Context) error) *MockConfigService_Reload_Call { | |||
| 855 | + | _c.Call.Return(run) | |||
| 856 | + | return _c | |||
| 857 | + | } | |||
| 858 | + | ||||
| 808 | // RemoveAttachment provides a mock function for the type MockConfigService | 859 | // RemoveAttachment provides a mock function for the type MockConfigService | ||
| 809 | func (_mock *MockConfigService) RemoveAttachment(ctx context.Context, domainOrGroup string, image string) error { | 860 | func (_mock *MockConfigService) RemoveAttachment(ctx context.Context, domainOrGroup string, image string) error { | ||
| 810 | ret := _mock.Called(ctx, domainOrGroup, image) | 861 | ret := _mock.Called(ctx, domainOrGroup, image) | ||
M internal/usecase/config/service.go +19 -0
| @@ -85,6 +85,25 @@ func (s *Service) Load(ctx context.Context) error { | |||
| 85 | 85 | return nil | |
| 86 | 86 | } | |
| 87 | 87 | ||
| 88 | + | // Reload re-reads the configuration file from disk and loads it into memory. | |
| 89 | + | // This should be used when you want to pick up external changes to the config file. | |
| 90 | + | // It differs from Load() which only loads from cached viper values. | |
| 91 | + | func (s *Service) Reload(ctx context.Context) error { | |
| 92 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| 93 | + | zerowrap.FieldLayer: "usecase", | |
| 94 | + | zerowrap.FieldUseCase: "Reload", | |
| 95 | + | }) | |
| 96 | + | log := zerowrap.FromCtx(ctx) | |
| 97 | + | ||
| 98 | + | // Re-read the config file from disk | |
| 99 | + | if err := s.viper.ReadInConfig(); err != nil { | |
| 100 | + | return log.WrapErr(err, "failed to read config file") | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // Load the values into memory | |
| 104 | + | return s.Load(ctx) | |
| 105 | + | } | |
| 106 | + | ||
| 88 | 107 | // loadConfigValues loads simple config values from viper. | |
| 89 | 108 | func (s *Service) loadConfigValues() Config { | |
| 90 | 109 | // Prefer gordon_domain over registry_domain | |
| @@ -85,6 +85,25 @@ func (s *Service) Load(ctx context.Context) error { | |||||
| 85 | return nil | 85 | return nil | ||
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | + | // Reload re-reads the configuration file from disk and loads it into memory. | |||
| 89 | + | // This should be used when you want to pick up external changes to the config file. | |||
| 90 | + | // It differs from Load() which only loads from cached viper values. | |||
| 91 | + | func (s *Service) Reload(ctx context.Context) error { | |||
| 92 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |||
| 93 | + | zerowrap.FieldLayer: "usecase", | |||
| 94 | + | zerowrap.FieldUseCase: "Reload", | |||
| 95 | + | }) | |||
| 96 | + | log := zerowrap.FromCtx(ctx) | |||
| 97 | + | ||||
| 98 | + | // Re-read the config file from disk | |||
| 99 | + | if err := s.viper.ReadInConfig(); err != nil { | |||
| 100 | + | return log.WrapErr(err, "failed to read config file") | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // Load the values into memory | |||
| 104 | + | return s.Load(ctx) | |||
| 105 | + | } | |||
| 106 | + | ||||
| 88 | // loadConfigValues loads simple config values from viper. | 107 | // loadConfigValues loads simple config values from viper. | ||
| 89 | func (s *Service) loadConfigValues() Config { | 108 | func (s *Service) loadConfigValues() Config { | ||
| 90 | // Prefer gordon_domain over registry_domain | 109 | // Prefer gordon_domain over registry_domain | ||