| Index: service/datastore/datastore.go
|
| diff --git a/service/datastore/datastore.go b/service/datastore/datastore.go
|
| index 68fae10a88300f0fe0773b21bfb45a09654af4a1..0389ab8914415b197f88abc87911eb05541587b9 100644
|
| --- a/service/datastore/datastore.go
|
| +++ b/service/datastore/datastore.go
|
| @@ -6,9 +6,17 @@ package datastore
|
|
|
| import (
|
| "fmt"
|
| + "io"
|
| + "io/ioutil"
|
| + "os"
|
| + "path/filepath"
|
| "reflect"
|
| + "runtime"
|
| + "strings"
|
|
|
| "github.com/luci/luci-go/common/errors"
|
| +
|
| + "gopkg.in/yaml.v2"
|
| )
|
|
|
| type datastoreImpl struct {
|
| @@ -348,3 +356,86 @@ func (d *datastoreImpl) DeleteMulti(keys []*Key) (err error) {
|
| func (d *datastoreImpl) Raw() RawInterface {
|
| return d.RawInterface
|
| }
|
| +
|
| +// ParseIndexYAML parses the contents of a index YAML file into a list of
|
| +// IndexDefinitions.
|
| +func ParseIndexYAML(content io.Reader) ([]*IndexDefinition, error) {
|
| + serialized, err := ioutil.ReadAll(content)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| +
|
| + var m map[string][]*IndexDefinition
|
| + if err := yaml.Unmarshal(serialized, &m); err != nil {
|
| + return nil, err
|
| + }
|
| +
|
| + if _, ok := m["indexes"]; !ok {
|
| + return nil, fmt.Errorf("datastore: missing key `indexes`: %v", m)
|
| + }
|
| + return m["indexes"], nil
|
| +}
|
| +
|
| +// getCallingTestFilePath looks up the call stack until the specified
|
| +// maxStackDepth and returns the absolute path of the first source filename
|
| +// ending with `_test.go`. If no test file is found, getCallingTestFilePath
|
| +// returns a non-nil error.
|
| +func getCallingTestFilePath(maxStackDepth int) (string, error) {
|
| + pcs := make([]uintptr, maxStackDepth)
|
| +
|
| + for _, pc := range pcs[:runtime.Callers(0, pcs)] {
|
| + path, _ := runtime.FuncForPC(pc - 1).FileLine(pc - 1)
|
| + if filename := filepath.Base(path); strings.HasSuffix(filename, "_test.go") {
|
| + return path, nil
|
| + }
|
| + }
|
| +
|
| + return "", fmt.Errorf("datastore: failed to determine source file name")
|
| +}
|
| +
|
| +// FindAndParseIndexYAML walks up from the directory specified by path until it
|
| +// finds a `index.yaml` or `index.yml` file. If an index YAML file
|
| +// is found, it opens and parses the file, and returns all the indexes found.
|
| +// If path is a relative path, it is converted into an absolute path
|
| +// relative to the calling test file. To determine the path of the calling test
|
| +// file, FindAndParseIndexYAML walks upto a maximum of 100 call stack frames
|
| +// looking for a file ending with `_test.go`.
|
| +//
|
| +// FindAndParseIndexYAML returns a non-nil error if the root of the drive is
|
| +// reached without finding an index YAML file, if there was
|
| +// an error reading the found index YAML file, or if the calling test file could
|
| +// not be located in the case of a relative path argument.
|
| +func FindAndParseIndexYAML(path string) ([]*IndexDefinition, error) {
|
| + var currentDir string
|
| +
|
| + if filepath.IsAbs(path) {
|
| + currentDir = path
|
| + } else {
|
| + testPath, err := getCallingTestFilePath(100)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + currentDir = filepath.Join(filepath.Dir(testPath), path)
|
| + }
|
| +
|
| + isRoot := func(dir string) bool {
|
| + parentDir := filepath.Dir(dir)
|
| + return os.IsPathSeparator(dir[len(dir)-1]) && os.IsPathSeparator(parentDir[len(parentDir)-1])
|
| + }
|
| +
|
| + for {
|
| + for _, filename := range []string{"index.yml", "index.yaml"} {
|
| + file, err := os.Open(filepath.Join(currentDir, filename))
|
| + if err == nil {
|
| + defer file.Close()
|
| + return ParseIndexYAML(file)
|
| + }
|
| + }
|
| +
|
| + if isRoot(currentDir) {
|
| + return nil, fmt.Errorf("datastore: failed to find index YAML file")
|
| + }
|
| +
|
| + currentDir = filepath.Dir(currentDir)
|
| + }
|
| +}
|
|
|