| Index: server/config/service.go
|
| diff --git a/server/config/service.go b/server/config/service.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6900dbcb54cad46752e2f72af4a630cd1fb0d3fa
|
| --- /dev/null
|
| +++ b/server/config/service.go
|
| @@ -0,0 +1,164 @@
|
| +// Copyright 2016 The LUCI Authors. All rights reserved.
|
| +// Use of this source code is governed under the Apache License, Version 2.0
|
| +// that can be found in the LICENSE file.
|
| +
|
| +package config
|
| +
|
| +import (
|
| + "net/http"
|
| + "net/url"
|
| + "sync"
|
| +
|
| + "github.com/luci/luci-go/common/config"
|
| + "github.com/luci/luci-go/common/config/impl/remote"
|
| + "github.com/luci/luci-go/common/errors"
|
| + "github.com/luci/luci-go/server/auth"
|
| +
|
| + "golang.org/x/net/context"
|
| +)
|
| +
|
| +// ClientProvider returns a config.Interface for the supplied parameters.
|
| +type ClientProvider interface {
|
| + GetConfigClient(context.Context, Authority) config.Interface
|
| +}
|
| +
|
| +// ClientBackend returns a Backend implementation that falls through to the
|
| +// supplied client config.Interface supplied by the Provider.
|
| +//
|
| +// url is the base URL to the configuration service, e.g.,
|
| +// https://example.appspot.com.
|
| +type ClientBackend struct {
|
| + Provider ClientProvider
|
| +}
|
| +
|
| +var _ Backend = (*ClientBackend)(nil)
|
| +
|
| +// ServiceURL implements Backend.
|
| +func (cb *ClientBackend) ServiceURL(c context.Context) url.URL {
|
| + return cb.getIface(c, AsAnonymous).ServiceURL(c)
|
| +}
|
| +
|
| +// ConfigSetURL implements Backend.
|
| +func (cb *ClientBackend) ConfigSetURL(c context.Context, a Authority, configSet string) (url.URL, error) {
|
| + u, err := cb.getIface(c, a).GetConfigSetLocation(c, configSet)
|
| + if err != nil || u == nil {
|
| + return url.URL{}, err
|
| + }
|
| + return *u, nil
|
| +}
|
| +
|
| +// Get implements Backend.
|
| +func (cb *ClientBackend) Get(c context.Context, configSet, path string, p Params) (*Item, error) {
|
| + svc := cb.getIface(c, p.Authority)
|
| +
|
| + cfg, err := svc.GetConfig(c, configSet, path, !p.Content)
|
| + if err != nil {
|
| + return nil, translateConfigErr(err)
|
| + }
|
| +
|
| + return makeItem(cfg), nil
|
| +}
|
| +
|
| +// GetAll implements Backend.
|
| +func (cb *ClientBackend) GetAll(c context.Context, t GetAllType, path string, p Params) ([]*Item, error) {
|
| + svc := cb.getIface(c, p.Authority)
|
| +
|
| + var fn func(context.Context, string, bool) ([]config.Config, error)
|
| + switch t {
|
| + case Project:
|
| + fn = svc.GetProjectConfigs
|
| + case Ref:
|
| + fn = svc.GetRefConfigs
|
| + default:
|
| + return nil, errors.Reason("unknown GetAllType: %(type)q").D("type", t).Err()
|
| + }
|
| +
|
| + cfgs, err := fn(c, path, !p.Content)
|
| + if err != nil || len(cfgs) == 0 {
|
| + return nil, translateConfigErr(err)
|
| + }
|
| +
|
| + items := make([]*Item, len(cfgs))
|
| + for i := range cfgs {
|
| + items[i] = makeItem(&cfgs[i])
|
| + }
|
| + return items, nil
|
| +}
|
| +
|
| +func (cb *ClientBackend) getIface(c context.Context, a Authority) config.Interface {
|
| + return cb.Provider.GetConfigClient(c, a)
|
| +}
|
| +
|
| +// RemoteClientProvider is a ClientProvider implementation that binds to
|
| +// a remote configuration service.
|
| +type RemoteClientProvider struct {
|
| + // BaseURL is the base URL to the configuration service, e.g.,
|
| + // https://example.appspot.com.
|
| + BaseURL string
|
| +
|
| + cacheLock sync.RWMutex
|
| + cache map[Authority]config.Interface
|
| +
|
| + // testUserDelegationToken, if not nil, is the delegation token to use for
|
| + // AsUser calls. This is done to mock delegation token generation.
|
| + testUserDelegationToken string
|
| +}
|
| +
|
| +var _ ClientProvider = (*RemoteClientProvider)(nil)
|
| +
|
| +// GetConfigClient implements ClientProvider.
|
| +func (p *RemoteClientProvider) GetConfigClient(c context.Context, a Authority) config.Interface {
|
| + p.cacheLock.RLock()
|
| + impl, ok := p.cache[a]
|
| + p.cacheLock.RUnlock()
|
| + if ok {
|
| + return impl
|
| + }
|
| +
|
| + p.cacheLock.Lock()
|
| + defer p.cacheLock.Unlock()
|
| +
|
| + if impl, ok := p.cache[a]; ok {
|
| + return impl
|
| + }
|
| +
|
| + // Create our remote implementation.
|
| + impl = remote.New(p.BaseURL+"/_ah/api/config/v1/", func(c context.Context) (*http.Client, error) {
|
| + var opts []auth.RPCOption
|
| + if a == AsUser && p.testUserDelegationToken != "" {
|
| + opts = append(opts, auth.WithDelegationToken(p.testUserDelegationToken))
|
| + }
|
| + t, err := auth.GetRPCTransport(c, a.rpcAuthorityKind(), opts...)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + return &http.Client{Transport: t}, nil
|
| + })
|
| + if p.cache == nil {
|
| + p.cache = make(map[Authority]config.Interface, 3)
|
| + }
|
| + p.cache[a] = impl
|
| +
|
| + return impl
|
| +}
|
| +
|
| +func translateConfigErr(err error) error {
|
| + switch err {
|
| + case config.ErrNoConfig:
|
| + return ErrNoConfig
|
| + default:
|
| + return err
|
| + }
|
| +}
|
| +
|
| +func makeItem(cfg *config.Config) *Item {
|
| + return &Item{
|
| + Meta: Meta{
|
| + ConfigSet: cfg.ConfigSet,
|
| + Path: cfg.Path,
|
| + ContentHash: cfg.ContentHash,
|
| + Revision: cfg.Revision,
|
| + },
|
| + Content: cfg.Content,
|
| + }
|
| +}
|
|
|