blob: 9216ea602d33654db314fc749274d1f2f1d8a82e [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gaeconfig
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"google.golang.org/grpc/credentials"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/gae/service/info"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/config"
"go.chromium.org/luci/config/cfgclient"
"go.chromium.org/luci/config/impl/erroring"
"go.chromium.org/luci/config/vars"
)
// devCfgDir is a name of the directory with config files when running in
// local dev appserver model. See Use for details.
const devCfgDir = "devcfg"
// defaultLazyConfigClient is the lazyConfigClient that will be shared by
// all requests.
var defaultLazyConfigClient = newLazyConfigClient(func(ctx context.Context) config.Interface {
return newClientFromSettings(ctx, mustFetchCachedSettings(ctx))
})
// Use installs the default luci-config client.
//
// The client is configured to use luci-config URL specified in the settings,
// using GAE app service account for authentication.
//
// If running in prod, and the settings don't specify luci-config URL, produces
// an implementation that returns a "not configured" error from all methods.
//
// If running on devserver, and the settings don't specify luci-config URL,
// returns a filesystem-based implementation that reads configs from a directory
// (or a symlink) named 'devcfg' located in the GAE module directory (where
// app.yaml is) or its immediate parent directory.
//
// If such directory can not be located, produces an implementation of that
// returns errors from all methods.
//
// Panics if it can't load the settings (should not happen since they are in
// the local memory cache usually).
func Use(ctx context.Context) context.Context {
return cfgclient.Use(ctx, defaultLazyConfigClient)
}
// devServerConfigsDir finds a directory with configs to use on the dev server.
func devServerConfigsDir() (string, error) {
pwd := os.Getenv("PWD") // os.Getwd works funny with symlinks, use PWD
candidates := []string{
filepath.Join(pwd, devCfgDir),
filepath.Join(filepath.Dir(pwd), devCfgDir),
}
for _, dir := range candidates {
if _, err := os.Stat(dir); err == nil {
return dir, nil
}
}
return "", fmt.Errorf("luci-config: could not find local configs in any of %s", candidates)
}
// newClientFromSettings instantiates a LUCI Config client based on settings.
func newClientFromSettings(c context.Context, s *Settings) config.Interface {
var configsDir string
if s.ConfigServiceHost == "" && info.IsDevAppServer(c) {
var err error
if configsDir, err = devServerConfigsDir(); err != nil {
return erroring.New(err)
}
}
client, err := cfgclient.New(c, cfgclient.Options{
Vars: &vars.Vars,
ServiceHost: s.ConfigServiceHost,
ConfigsDir: configsDir,
ClientFactory: func(ctx context.Context) (*http.Client, error) {
t, err := auth.GetRPCTransport(ctx, auth.AsSelf, auth.WithScopes(auth.CloudOAuthScopes...))
if err != nil {
return nil, err
}
return &http.Client{Transport: t}, nil
},
GetPerRPCCredsFn: func(ctx context.Context) (credentials.PerRPCCredentials, error) {
creds, err := auth.GetPerRPCCredentials(ctx,
auth.AsSelf,
auth.WithIDTokenAudience("https://"+s.ConfigServiceHost),
)
if err != nil {
return nil, errors.Annotate(err, "failed to get credentials to access %s", s.ConfigServiceHost).Err()
}
return creds, nil
},
UserAgent: info.AppID(c),
})
if err != nil {
return erroring.New(err)
}
return client
}