Skip to content

Commit 4986f99

Browse files
committed
POC: watch config files to load/unload maps
1 parent f5aa603 commit 4986f99

27 files changed

+3576
-29
lines changed

atlas/atlas.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package atlas
33

44
import (
55
"context"
6+
"fmt"
67
"os"
78
"strconv"
89
"strings"
@@ -221,6 +222,52 @@ func (a *Atlas) AddMap(m Map) {
221222
a.maps[m.Name] = m
222223
}
223224

225+
// AddMaps registers maps by name, all or nothing. If a map already exists an error will be returned.
226+
func (a *Atlas) AddMaps(maps []Map) error {
227+
if a == nil {
228+
// Use the default Atlas if a, is nil. This way the empty value is
229+
// still useful.
230+
return defaultAtlas.AddMaps(maps)
231+
}
232+
a.Lock()
233+
defer a.Unlock()
234+
235+
if a.maps == nil {
236+
a.maps = map[string]Map{}
237+
}
238+
239+
// Check all the names for conflicts before we add any map, so that we can add all or none.
240+
for _, m := range maps {
241+
if _, exists := a.maps[m.Name]; exists {
242+
return fmt.Errorf("Map with name \"%s\" already exists.", m.Name)
243+
}
244+
}
245+
246+
// Now add all the maps.
247+
for _, m := range maps {
248+
a.maps[m.Name] = m
249+
}
250+
251+
return nil
252+
}
253+
254+
func (a *Atlas) RemoveMaps(names []string) {
255+
if a == nil {
256+
// Use the default Atlas if a, is nil. This way the empty value is
257+
// still useful.
258+
defaultAtlas.RemoveMaps(names)
259+
return
260+
}
261+
a.Lock()
262+
defer a.Unlock()
263+
264+
for _, name := range names {
265+
if _, exists := a.maps[name]; exists {
266+
delete(a.maps, name)
267+
}
268+
}
269+
}
270+
224271
// GetCache returns the registered cache if one is registered, otherwise nil
225272
func (a *Atlas) GetCache() cache.Interface {
226273
if a == nil {

cmd/internal/register/maps.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ func Maps(a *atlas.Atlas, maps []provider.Map, providers map[string]provider.Til
131131
)
132132

133133
// iterate our maps
134+
newMaps := make([]atlas.Map, 0, len(maps))
134135
for _, m := range maps {
135136
newMap := webMercatorMapFromConfigMap(m)
136137

@@ -157,9 +158,15 @@ func Maps(a *atlas.Atlas, maps []provider.Map, providers map[string]provider.Til
157158
newMap.Layers = append(newMap.Layers, layer)
158159
}
159160

160-
a.AddMap(newMap)
161+
newMaps = append(newMaps, newMap)
161162
}
162-
return nil
163+
164+
// Register all or nothing.
165+
return a.AddMaps(newMaps)
166+
}
167+
168+
func UnloadMaps(a *atlas.Atlas, names []string) {
169+
a.RemoveMaps(names)
163170
}
164171

165172
// Find allow HTML tag

cmd/tegola/cmd/root.go

Lines changed: 135 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package cmd
22

33
import (
4+
"context"
45
"fmt"
56

67
"github.com/go-spatial/cobra"
78
"github.com/go-spatial/tegola/atlas"
89
"github.com/go-spatial/tegola/cmd/internal/register"
910
cachecmd "github.com/go-spatial/tegola/cmd/tegola/cmd/cache"
1011
"github.com/go-spatial/tegola/config"
12+
"github.com/go-spatial/tegola/config/source"
1113
"github.com/go-spatial/tegola/dict"
1214
"github.com/go-spatial/tegola/internal/build"
15+
"github.com/go-spatial/tegola/internal/env"
1316
"github.com/go-spatial/tegola/internal/log"
17+
"github.com/go-spatial/tegola/provider"
1418
)
1519

1620
var (
@@ -114,22 +118,22 @@ func initConfig(configFile string, cacheRequired bool, logLevel string, logger s
114118
return err
115119
}
116120

117-
// init our providers
118-
// but first convert []env.Map -> []dict.Dicter
119-
provArr := make([]dict.Dicter, len(conf.Providers))
120-
for i := range provArr {
121-
provArr[i] = conf.Providers[i]
121+
// Init providers from the primary config file.
122+
providers, err := initProviders(conf.Providers, conf.Maps)
123+
if err != nil {
124+
return err
122125
}
123126

124-
providers, err := register.Providers(provArr, conf.Maps)
125-
if err != nil {
126-
return fmt.Errorf("could not register providers: %v", err)
127+
// Init maps from the primary config file.
128+
if err = initMaps(conf.Maps, providers); err != nil {
129+
return err
127130
}
128131

129-
// init our maps
130-
if err = register.Maps(nil, conf.Maps, providers); err != nil {
131-
return fmt.Errorf("could not register maps: %v", err)
132+
// Setup the app config source.
133+
if err = initAppConfigSource(conf); err != nil {
134+
return err
132135
}
136+
133137
if len(conf.Cache) == 0 && cacheRequired {
134138
return fmt.Errorf("no cache defined in config, please check your config (%v)", configFile)
135139
}
@@ -152,3 +156,123 @@ func initConfig(configFile string, cacheRequired bool, logLevel string, logger s
152156
atlas.SetObservability(observer)
153157
return nil
154158
}
159+
160+
// initProviders translate provider config from a TOML file into usable Provider objects.
161+
func initProviders(providersConfig []env.Dict, maps []provider.Map) (map[string]provider.TilerUnion, error) {
162+
// first convert []env.Map -> []dict.Dicter
163+
provArr := make([]dict.Dicter, len(providersConfig))
164+
for i := range provArr {
165+
provArr[i] = providersConfig[i]
166+
}
167+
168+
providers, err := register.Providers(provArr, conf.Maps)
169+
if err != nil {
170+
return nil, fmt.Errorf("could not register providers: %v", err)
171+
}
172+
173+
return providers, nil
174+
}
175+
176+
// initMaps registers maps with Atlas to be ready for service.
177+
func initMaps(maps []provider.Map, providers map[string]provider.TilerUnion) error {
178+
if err := register.Maps(nil, maps, providers); err != nil {
179+
return fmt.Errorf("could not register maps: %v", err)
180+
}
181+
182+
return nil
183+
}
184+
185+
// initAppConfigSource sets up an additional configuration source for "apps" (groups of providers and maps) to be loaded and unloaded on-the-fly.
186+
func initAppConfigSource(conf config.Config) error {
187+
// Get the config source type. If none, return.
188+
val, err := conf.AppConfigSource.String("type", nil)
189+
if err != nil || val == "" {
190+
return nil
191+
}
192+
193+
// Initialize the source.
194+
ctx := context.Background() // Not doing anything with context now, but could use it for stopping this goroutine.
195+
src, err := source.InitSource(val, conf.AppConfigSource, conf.BaseDir)
196+
if err != nil {
197+
return err
198+
}
199+
200+
// Load and start watching for new apps.
201+
watcher, err := src.LoadAndWatch(ctx)
202+
if err != nil {
203+
return err
204+
}
205+
206+
go func() {
207+
// Keep a record of what we've loaded so that we can unload when needed.
208+
apps := make(map[string]source.App)
209+
210+
for {
211+
select {
212+
case app, ok := <-watcher.Updates:
213+
if !ok {
214+
return
215+
}
216+
217+
// Check for validity first.
218+
if err := config.ValidateApp(&app); err != nil {
219+
log.Errorf("Failed validating app %s. %s", app.Key, err)
220+
}
221+
222+
// If the new app is named the same as an existing app, first unload the existing one.
223+
if old, exists := apps[app.Key]; exists {
224+
log.Infof("Unloading app %s...", old.Key)
225+
// We need only unload maps, since the providers don't live outside of maps.
226+
register.UnloadMaps(nil, getMapNames(old))
227+
delete(apps, app.Key)
228+
}
229+
230+
log.Infof("Loading app %s...", app.Key)
231+
232+
// Init new providers
233+
providers, err := initProviders(app.Providers, app.Maps)
234+
if err != nil {
235+
log.Errorf("Failed initializing providers from %s: %s", app.Key, err)
236+
continue
237+
}
238+
239+
// Init new maps
240+
if err = initMaps(app.Maps, providers); err != nil {
241+
log.Errorf("Failed initializing maps from %s: %s", app.Key, err)
242+
continue
243+
}
244+
245+
// Record that we've loaded this app.
246+
apps[app.Key] = app
247+
248+
case deleted, ok := <-watcher.Deletions:
249+
if !ok {
250+
return
251+
}
252+
253+
// Unload an app's maps if it was previously loaded.
254+
if app, exists := apps[deleted]; exists {
255+
log.Infof("Unloading app %s...", app.Key)
256+
register.UnloadMaps(nil, getMapNames(app))
257+
delete(apps, app.Key)
258+
} else {
259+
log.Infof("Received an unload event for app %s, but couldn't find it.", deleted)
260+
}
261+
262+
case <-ctx.Done():
263+
return
264+
}
265+
}
266+
}()
267+
268+
return nil
269+
}
270+
271+
func getMapNames(app source.App) []string {
272+
names := make([]string, 0, len(app.Maps))
273+
for _, m := range app.Maps {
274+
names = append(names, string(m.Name))
275+
}
276+
277+
return names
278+
}

0 commit comments

Comments
 (0)