зеркало из https://github.com/golang/build.git
225 строки
5.8 KiB
Go
225 строки
5.8 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/gob"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/build/gerrit"
|
|
"golang.org/x/build/maintner/godata"
|
|
)
|
|
|
|
// GerritAccounts holds a mapping of Gerrit account IDs to
|
|
// the corresponding gerrit.AccountInfo object.
|
|
// A call to Initialize must be made in order for the map to be populated.
|
|
type GerritAccounts struct {
|
|
accounts map[int64]*gerrit.AccountInfo // Gerrit account ID to AccountInfo.
|
|
refreshTime time.Time
|
|
}
|
|
|
|
// ErrNotFound is the error returned when no mapping for a Gerrit email address is available.
|
|
var ErrNotFound = errors.New("no mapping found for the given Gerrit email address")
|
|
|
|
// LookupByGerritEmail translates a Gerrit email address in the format of
|
|
// <Gerrit User ID>@<Gerrit server UUID> into the actual email address of the person.
|
|
// If the cache is out of date, and fetchUpdates is true, it'll download a fresh mapping from Gerrit,
|
|
// and persist it as well. If fetchUpdates is false, then ErrNotFound is returned.
|
|
// After downloading a fresh mapping, and a mapping for an account ID is not found,
|
|
// then ErrNotFound is returned.
|
|
func (ga *GerritAccounts) LookupByGerritEmail(gerritEmail string, fetchUpdates bool) (*gerrit.AccountInfo, error) {
|
|
if gerritEmail == "" {
|
|
return nil, errors.New("gerritEmail cannot be empty")
|
|
}
|
|
|
|
atIdx := strings.LastIndex(gerritEmail, "@")
|
|
if atIdx == -1 {
|
|
return nil, fmt.Errorf("LookupByGerritEmail: %q is not a valid email address", gerritEmail)
|
|
}
|
|
|
|
accountId, err := strconv.Atoi(gerritEmail[0:atIdx])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("LookupByGerritEmail: %q is not of the form <Gerrit User ID>@<Gerrit server UUID>", gerritEmail)
|
|
}
|
|
|
|
account := ga.accounts[int64(accountId)]
|
|
if account != nil {
|
|
// The cached mapping might be the same as gerritEmail (as it's the default if a mapping is missing).
|
|
// Return ErrNotFound in that case.
|
|
if account.Email == gerritEmail {
|
|
return nil, ErrNotFound
|
|
}
|
|
return account, nil
|
|
}
|
|
|
|
if !fetchUpdates {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
// Cache miss, let's sync up with Gerrit.
|
|
|
|
// We should also add a default value for this email address - in case
|
|
// Gerrit doesn't have this account ID (which would be rare - or the account is inactive),
|
|
// we don't want to keep making network calls.
|
|
// As GerritAccounts holds a map, if Gerrit returns a valid mapping,
|
|
// it will be overridden.
|
|
ga.accounts[int64(accountId)] = &gerrit.AccountInfo{
|
|
Email: gerritEmail,
|
|
NumericID: int64(accountId),
|
|
Name: gerritEmail,
|
|
Username: gerritEmail,
|
|
}
|
|
|
|
// If we've recently hit Gerrit for a fresh mapping already, then skip a network call,
|
|
// and persist the default version for this gerritEmail.
|
|
if time.Now().Sub(ga.refreshTime).Minutes() < 5 {
|
|
log.Println("Skipping Gerrit account info lookup for", gerritEmail)
|
|
err = ga.cacheMappingToDisk()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
if err := ga.fetchAndPersist(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ga.accounts[int64(accountId)].Email == gerritEmail {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return ga.accounts[int64(accountId)], nil
|
|
}
|
|
|
|
// refresh makes a call to the Gerrit server, and updates the mapping.
|
|
// It also updates refreshTime, after the update has completed.
|
|
func (ga *GerritAccounts) refresh() error {
|
|
if ga.accounts == nil {
|
|
ga.accounts = map[int64]*gerrit.AccountInfo{}
|
|
}
|
|
|
|
c := gerrit.NewClient("https://go-review.googlesource.com", gerrit.NoAuth)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
start := 0
|
|
for {
|
|
accounts, err := c.QueryAccounts(ctx, "is:active",
|
|
gerrit.QueryAccountsOpt{Fields: []string{"DETAILS"}, Start: start})
|
|
|
|
if err != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
start += len(accounts)
|
|
|
|
for _, account := range accounts {
|
|
ga.accounts[account.NumericID] = account
|
|
}
|
|
|
|
log.Println("Fetched", start, "accounts from Gerrit")
|
|
|
|
if accounts[len(accounts)-1].MoreAccounts == false {
|
|
break
|
|
}
|
|
}
|
|
|
|
ga.refreshTime = time.Now()
|
|
|
|
return nil
|
|
}
|
|
|
|
// cacheMappingToDisk serializes the map and writes it to the cache directory.
|
|
func (ga *GerritAccounts) cacheMappingToDisk() error {
|
|
cachePath, err := cachePath()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
encoder := gob.NewEncoder(&out)
|
|
|
|
err = encoder.Encode(ga.accounts)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ioutil.WriteFile(cachePath, out.Bytes(), 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Initialize does either one of the following two things, in order:
|
|
// 1. If a cached mapping exists, then restore the map from the cache and return.
|
|
// 2. If the cached mapping does not exist, hit Gerrit (call refresh()), and then persist the mapping.
|
|
func (ga *GerritAccounts) Initialize() error {
|
|
cachePath, err := cachePath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cache, err := ioutil.ReadFile(cachePath); err == nil {
|
|
d := gob.NewDecoder(bytes.NewReader(cache))
|
|
|
|
if err := d.Decode(&ga.accounts); err != nil {
|
|
return err
|
|
}
|
|
log.Println("Read Gerrit accounts information from disk cache")
|
|
return nil
|
|
}
|
|
|
|
if err := ga.fetchAndPersist(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ga *GerritAccounts) fetchAndPersist() error {
|
|
log.Println("Fetching accounts mapping from Gerrit. This will take some time...")
|
|
|
|
err := ga.refresh()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ga.cacheMappingToDisk()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func cachePath() (string, error) {
|
|
targetDir := godata.XdgCacheDir()
|
|
targetDir = filepath.Join(targetDir, "golang-build-cmd-cl")
|
|
if err := os.MkdirAll(targetDir, 0700); err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(targetDir, "accounts"), nil
|
|
}
|