| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package gaeconfig | |
| 6 | |
| 7 import ( | |
| 8 "errors" | |
| 9 "fmt" | |
| 10 "net/http" | |
| 11 "os" | |
| 12 "path/filepath" | |
| 13 "sync" | |
| 14 "time" | |
| 15 | |
| 16 "golang.org/x/net/context" | |
| 17 | |
| 18 "github.com/luci/gae/service/info" | |
| 19 "github.com/luci/luci-go/common/config" | |
| 20 "github.com/luci/luci-go/common/config/impl/erroring" | |
| 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 ) | |
| 25 | |
| 26 // ErrNotConfigured is returned by methods of config.Interface object returned | |
| 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") | |
| 29 | |
| 30 // devCfgDir is a name of the directory with config files when running in | |
| 31 // local dev appserver model. See New for details. | |
| 32 const devCfgDir = "devcfg" | |
| 33 | |
| 34 // implCache is used to avoid reallocating config.Interface implementation | |
| 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 // | |
| 50 // The client is configured to use luci-config URL specified in the settings, | |
| 51 // using GAE app service account for authentication. | |
| 52 // | |
| 53 // 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 | |
| 55 // methods. | |
| 56 // | |
| 57 // 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 | |
| 59 // (or a symlink) named 'devcfg' located in the GAE module directory (where | |
| 60 // app.yaml is) or its immediate parent directory. | |
| 61 // | |
| 62 // If such directory can not be located, produces an implementation of | |
| 63 // config.Interface that returns errors from all methods. | |
| 64 // | |
| 65 // Panics if it can't load the settings (should not happen since they are in | |
| 66 // the local memory cache usually). | |
| 67 func New(c context.Context) config.Interface { | |
| 68 settings, err := FetchCachedSettings(c) | |
| 69 if err != nil { | |
| 70 panic(err) | |
| 71 } | |
| 72 | |
| 73 if settings.ConfigServiceURL == "" { | |
| 74 if info.IsDevAppServer(c) { | |
| 75 return devServerConfig() | |
| 76 } | |
| 77 return erroring.New(ErrNotConfigured) | |
| 78 } | |
| 79 | |
| 80 return configImplForSettings(settings) | |
| 81 } | |
| 82 | |
| 83 // configImplForSettings returns config.Interface based on given settings. | |
| 84 // | |
| 85 // Split out from New to enforce that returned config.Interface doesn't depend | |
| 86 // on the context (since this function doesn't accept a context). | |
| 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 } | |
| 114 | |
| 115 // devServerConfig returns config.Interface to use on a devserver. | |
| 116 // | |
| 117 // See New for details. | |
| 118 func devServerConfig() config.Interface { | |
| 119 pwd := os.Getenv("PWD") // os.Getwd works funny with symlinks, use PWD | |
| 120 candidates := []string{ | |
| 121 filepath.Join(pwd, devCfgDir), | |
| 122 filepath.Join(filepath.Dir(pwd), devCfgDir), | |
| 123 } | |
| 124 for _, dir := range candidates { | |
| 125 if _, err := os.Stat(dir); err == nil { | |
| 126 fs, err := filesystem.New(dir) | |
| 127 if err != nil { | |
| 128 return erroring.New(err) | |
| 129 } | |
| 130 return fs | |
| 131 } | |
| 132 } | |
| 133 return erroring.New(fmt.Errorf("luci-config: could not find local config
s in any of %s", candidates)) | |
| 134 } | |
| 135 | |
| 136 // authenticatedClient returns http.Client to use for making authenticated | |
| 137 // request to the config service. | |
| 138 // | |
| 139 // The returned client uses GAE app's service account for authentication. | |
| 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 } | |
| OLD | NEW |