Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package gaeconfig | 5 package gaeconfig |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "errors" | 8 "errors" |
| 9 "fmt" | 9 "fmt" |
| 10 "net/http" | |
| 11 "os" | 10 "os" |
| 12 "path/filepath" | 11 "path/filepath" |
| 13 "sync" | |
| 14 "time" | 12 "time" |
| 15 | 13 |
| 16 » "golang.org/x/net/context" | 14 » "github.com/luci/luci-go/common/config/impl/filesystem" |
| 15 » "github.com/luci/luci-go/server/config" | |
| 16 » "github.com/luci/luci-go/server/config/caching" | |
| 17 » "github.com/luci/luci-go/server/config/erroring" | |
| 18 » "github.com/luci/luci-go/server/config/testconfig" | |
| 19 » "github.com/luci/luci-go/server/config/textproto" | |
| 17 | 20 |
| 18 "github.com/luci/gae/service/info" | 21 "github.com/luci/gae/service/info" |
| 19 » "github.com/luci/luci-go/common/config" | 22 |
| 20 » "github.com/luci/luci-go/common/config/impl/erroring" | 23 » "golang.org/x/net/context" |
| 21 » "github.com/luci/luci-go/common/config/impl/filesystem" | |
| 22 » "github.com/luci/luci-go/common/config/impl/remote" | |
| 23 » "github.com/luci/luci-go/server/auth" | |
| 24 ) | 24 ) |
| 25 | 25 |
| 26 // ErrNotConfigured is returned by methods of config.Interface object returned | 26 // ErrNotConfigured is returned by methods of config.Backend object returned |
| 27 // by New if config service URL is not set. Usually happens for new apps. | 27 // by New if config service URL is not set. Usually happens for new apps. |
| 28 var ErrNotConfigured = errors.New("config service URL is not set in settings") | 28 var ErrNotConfigured = errors.New("config service URL is not set in settings") |
| 29 | 29 |
| 30 // devCfgDir is a name of the directory with config files when running in | 30 // devCfgDir is a name of the directory with config files when running in |
| 31 // local dev appserver model. See New for details. | 31 // local dev appserver model. See New for details. |
| 32 const devCfgDir = "devcfg" | 32 const devCfgDir = "devcfg" |
| 33 | 33 |
| 34 // implCache is used to avoid reallocating config.Interface implementation | 34 // Use installs the default luci-config client. |
| 35 // during each request. | |
| 36 // | |
| 37 // config.Interface instances don't actually depend on the context and thus we | |
| 38 // can share them between requests. | |
| 39 // | |
| 40 // Cache key is the current value of Settings struct. We don't bother to clean | |
| 41 // up old entries, since in most cases there'll be none (the settings are mostly | |
| 42 // static). | |
| 43 var implCache struct { | |
| 44 » lock sync.RWMutex | |
| 45 » cache map[Settings]config.Interface | |
| 46 } | |
| 47 | |
| 48 // New constructs default luci-config client. | |
| 49 // | 35 // |
| 50 // The client is configured to use luci-config URL specified in the settings, | 36 // The client is configured to use luci-config URL specified in the settings, |
| 51 // using GAE app service account for authentication. | 37 // using GAE app service account for authentication. |
| 52 // | 38 // |
| 53 // If running in prod, and the settings don't specify luci-config URL, produces | 39 // If running in prod, and the settings don't specify luci-config URL, produces |
| 54 // an implementation of config.Interface that returns ErrNotConfigured from all | 40 // an implementation of config.Interface that returns ErrNotConfigured from all |
| 55 // methods. | 41 // methods. |
| 56 // | 42 // |
| 57 // If running on devserver, and the settings don't specify luci-config URL, | 43 // If running on devserver, and the settings don't specify luci-config URL, |
| 58 // returns a filesystem-based implementation that reads configs from a directory | 44 // returns a filesystem-based implementation that reads configs from a directory |
| 59 // (or a symlink) named 'devcfg' located in the GAE module directory (where | 45 // (or a symlink) named 'devcfg' located in the GAE module directory (where |
| 60 // app.yaml is) or its immediate parent directory. | 46 // app.yaml is) or its immediate parent directory. |
| 61 // | 47 // |
| 62 // If such directory can not be located, produces an implementation of | 48 // If such directory can not be located, produces an implementation of |
| 63 // config.Interface that returns errors from all methods. | 49 // config.Interface that returns errors from all methods. |
| 64 // | 50 // |
| 65 // Panics if it can't load the settings (should not happen since they are in | 51 // Panics if it can't load the settings (should not happen since they are in |
| 66 // the local memory cache usually). | 52 // the local memory cache usually). |
| 67 func New(c context.Context) config.Interface { | 53 func Use(c context.Context) context.Context { return useImpl(c, nil) } |
|
iannucci
2017/01/07 21:05:26
if you do the package renaming, this should move o
dnj
2017/01/10 03:30:07
I'll move to luci_config/appengine/gaeconfig.
| |
| 68 » settings, err := FetchCachedSettings(c) | 54 |
| 69 » if err != nil { | 55 func useImpl(c context.Context, backend config.Backend) context.Context { |
| 70 » » panic(err) | 56 » return installConfigBackend(c, mustFetchCachedSettings(c), backend) |
| 57 } | |
| 58 | |
| 59 func installConfigBackend(c context.Context, s *Settings, backend config.Backend ) context.Context { | |
| 60 » if backend == nil { | |
| 61 » » // Non-testing, build a Backend. | |
| 62 » » backend = getPrimaryBackend(c, s) | |
| 71 } | 63 } |
| 72 | 64 |
| 65 // Install a FormatRegistry. Register common config service protobufs wi th it. | |
| 66 c = withFormatRegistry(c, defaultFormatterRegistry()) | |
| 67 | |
| 68 backend = &config.FormatBackend{ | |
| 69 Backend: backend, | |
| 70 GetRegistry: GetFormatterRegistry, | |
| 71 } | |
| 72 | |
| 73 // Apply caching configuration. | |
| 74 exp := time.Duration(s.CacheExpirationSec) * time.Second | |
| 75 if exp > 0 { | |
| 76 // Add a ProcCache, backed by memcache. | |
| 77 backend = caching.ProcCache(backend, exp) | |
| 78 backend = memcacheBackend(backend, exp) | |
| 79 } | |
| 80 | |
| 81 c = config.WithBackend(c, backend) | |
| 82 return c | |
| 83 } | |
| 84 | |
| 85 func getPrimaryBackend(c context.Context, settings *Settings) (backend config.Ba ckend) { | |
| 86 // Identify our config service Backend (in testing, it will be supplied) . | |
| 73 if settings.ConfigServiceURL == "" { | 87 if settings.ConfigServiceURL == "" { |
| 74 if info.IsDevAppServer(c) { | 88 if info.IsDevAppServer(c) { |
| 75 » » » return devServerConfig() | 89 » » » return devServerBackend() |
| 76 } | 90 } |
| 77 return erroring.New(ErrNotConfigured) | 91 return erroring.New(ErrNotConfigured) |
| 78 } | 92 } |
| 79 | 93 » return &config.ClientBackend{&config.RemoteClientProvider{ |
| 80 » return configImplForSettings(settings) | 94 » » BaseURL: settings.ConfigServiceURL, |
| 95 » }} | |
| 81 } | 96 } |
| 82 | 97 |
| 83 // configImplForSettings returns config.Interface based on given settings. | 98 func defaultFormatterRegistry() *config.FormatterRegistry { |
| 84 // | 99 » var fr config.FormatterRegistry |
| 85 // Split out from New to enforce that returned config.Interface doesn't depend | 100 » textproto.RegisterFormatter(&fr) |
| 86 // on the context (since this function doesn't accept a context). | 101 » return &fr |
| 87 func configImplForSettings(settings Settings) config.Interface { | |
| 88 » implCache.lock.RLock() | |
| 89 » impl, ok := implCache.cache[settings] | |
| 90 » implCache.lock.RUnlock() | |
| 91 » if ok { | |
| 92 » » return impl | |
| 93 » } | |
| 94 | |
| 95 » implCache.lock.Lock() | |
| 96 » defer implCache.lock.Unlock() | |
| 97 | |
| 98 » if impl, ok := implCache.cache[settings]; ok { | |
| 99 » » return impl | |
| 100 » } | |
| 101 | |
| 102 » impl = remote.New(settings.ConfigServiceURL+"/_ah/api/config/v1/", authe nticatedClient) | |
| 103 » if settings.CacheExpirationSec != 0 { | |
| 104 » » impl = WrapWithCache(impl, time.Duration(settings.CacheExpiratio nSec)*time.Second) | |
| 105 » } | |
| 106 | |
| 107 » if implCache.cache == nil { | |
| 108 » » implCache.cache = make(map[Settings]config.Interface, 1) | |
| 109 » } | |
| 110 » implCache.cache[settings] = impl | |
| 111 | |
| 112 » return impl | |
| 113 } | 102 } |
| 114 | 103 |
| 115 // devServerConfig returns config.Interface to use on a devserver. | 104 // devServerConfig returns config.Interface to use on a devserver. |
| 116 // | 105 // |
| 117 // See New for details. | 106 // See New for details. |
| 118 func devServerConfig() config.Interface { | 107 func devServerBackend() config.Backend { |
| 119 pwd := os.Getenv("PWD") // os.Getwd works funny with symlinks, use PWD | 108 pwd := os.Getenv("PWD") // os.Getwd works funny with symlinks, use PWD |
| 120 candidates := []string{ | 109 candidates := []string{ |
| 121 filepath.Join(pwd, devCfgDir), | 110 filepath.Join(pwd, devCfgDir), |
| 122 filepath.Join(filepath.Dir(pwd), devCfgDir), | 111 filepath.Join(filepath.Dir(pwd), devCfgDir), |
| 123 } | 112 } |
| 124 for _, dir := range candidates { | 113 for _, dir := range candidates { |
| 125 if _, err := os.Stat(dir); err == nil { | 114 if _, err := os.Stat(dir); err == nil { |
| 126 fs, err := filesystem.New(dir) | 115 fs, err := filesystem.New(dir) |
| 127 if err != nil { | 116 if err != nil { |
| 128 return erroring.New(err) | 117 return erroring.New(err) |
| 129 } | 118 } |
| 130 » » » return fs | 119 » » » return &config.ClientBackend{&testconfig.LocalClientProv ider{ |
| 120 » » » » Base: fs, | |
| 121 » » » }} | |
| 131 } | 122 } |
| 132 } | 123 } |
| 133 return erroring.New(fmt.Errorf("luci-config: could not find local config s in any of %s", candidates)) | 124 return erroring.New(fmt.Errorf("luci-config: could not find local config s in any of %s", candidates)) |
| 134 } | 125 } |
| 135 | 126 |
| 136 // authenticatedClient returns http.Client to use for making authenticated | 127 var formatRegistryKey = "github.com/luci/luci-go/appengine/gaeconfig:formatRegis try" |
| 137 // request to the config service. | 128 |
| 138 // | 129 func withFormatRegistry(c context.Context, fr *config.FormatterRegistry) context .Context { |
| 139 // The returned client uses GAE app's service account for authentication. | 130 » return context.WithValue(c, &formatRegistryKey, fr) |
| 140 func authenticatedClient(ctx context.Context) (*http.Client, error) { | |
| 141 » t, err := auth.GetRPCTransport(ctx, auth.AsSelf) | |
| 142 » if err != nil { | |
| 143 » » return nil, err | |
| 144 » } | |
| 145 » return &http.Client{Transport: t}, nil | |
| 146 } | 131 } |
| 132 | |
| 133 // GetFormatterRegistry is used to retrieve the config service format registry | |
| 134 // from the supplied Context. | |
| 135 func GetFormatterRegistry(c context.Context) *config.FormatterRegistry { | |
| 136 return c.Value(&formatRegistryKey).(*config.FormatterRegistry) | |
| 137 } | |
| OLD | NEW |