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) |
+ } |
+} |