| Index: go/src/infra/gae/epservice/epservice.go
|
| diff --git a/go/src/infra/gae/epservice/epservice.go b/go/src/infra/gae/epservice/epservice.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8d46ae4a120dafe55b66d3053f69b418e21bd29b
|
| --- /dev/null
|
| +++ b/go/src/infra/gae/epservice/epservice.go
|
| @@ -0,0 +1,232 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// EPService will run a local appengine service with all of the endpoint
|
| +// services under this directory. The tool assumes that:
|
| +// * goapp exists in PATH (and understands all the import paths in the
|
| +// defined services)
|
| +package main
|
| +
|
| +import (
|
| + "bytes"
|
| + "encoding/json"
|
| + "flag"
|
| + "fmt"
|
| + "io/ioutil"
|
| + "os"
|
| + "os/exec"
|
| + "os/signal"
|
| + "path/filepath"
|
| + "regexp"
|
| + "strings"
|
| + "sync"
|
| + "text/template"
|
| + "time"
|
| +
|
| + "infra/libs/jsutil"
|
| +)
|
| +
|
| +var (
|
| + clearDatastore = flag.Bool("clear_datastore", false, "if set, clear the datastore.")
|
| + leak = flag.Bool("leak", false, "if set, leak the temporary directory.")
|
| + verbose = flag.Bool("verbose", false, "if set, print skipped packages.")
|
| + servicePackagesBase = flag.String("base", "infra",
|
| + "base Go package to walk to find service definitions.")
|
| +)
|
| +
|
| +var serviceScript = `{{define "go"}}
|
| +// DO NOT EDIT
|
| +// Auto-generated by infra/gae/epservice
|
| +// {{.Timestamp}}
|
| +
|
| +package main
|
| +
|
| +import (
|
| + "fmt"
|
| +
|
| +{{range $idx, $pkg := .Pkgs}}
|
| + pkg{{$idx}} "{{$pkg.Imp}}"{{end}}
|
| +
|
| + "github.com/GoogleCloudPlatform/go-endpoints/endpoints"
|
| +)
|
| +
|
| +func init() {
|
| + var err error
|
| + server := endpoints.NewServer("")
|
| +
|
| + {{range $idx, $pkg := .Pkgs }}
|
| + err = pkg{{$idx}}.RegisterEndpointsService(server)
|
| + if err != nil {
|
| + panic(fmt.Errorf("Error while registering service {{$pkg}}: %s", err))
|
| + }
|
| + {{end}}
|
| +
|
| + server.HandleHTTP(nil)
|
| +}
|
| +{{end}}
|
| +`
|
| +
|
| +const appYaml = `{{define "yaml"}}
|
| +# DO NOT EDIT
|
| +# Auto-generated by infra/gae/epservice
|
| +# {{.Timestamp}}
|
| +
|
| +application: epclient-tmp-app
|
| +version: nope
|
| +runtime: go
|
| +api_version: go1
|
| +
|
| +handlers:
|
| +- url: /.*
|
| + script: _go_app
|
| +{{end}}
|
| +`
|
| +
|
| +var templ = template.New("service")
|
| +
|
| +func init() {
|
| + template.Must(templ.Parse(serviceScript))
|
| + template.Must(templ.Parse(appYaml))
|
| +}
|
| +
|
| +type templInput struct {
|
| + Pkgs []Pkg
|
| + Timestamp time.Time
|
| +}
|
| +
|
| +// Pkg holds the import and real filesystem paths of an endpoint service
|
| +// package. It's exported merely for reflection purposes, since it's used
|
| +// by text/template.
|
| +type Pkg struct {
|
| + Imp string
|
| + Pth string
|
| +}
|
| +
|
| +func boom(err error) {
|
| + if err != nil {
|
| + panic(err)
|
| + }
|
| +}
|
| +
|
| +// NOTE: if you format your RegisterEndpointsService implementation like a bozo,
|
| +// then this won't match. Don't do that.
|
| +var reRegisterEndpointsService = regexp.MustCompile(
|
| + `\nfunc RegisterEndpointsService\(\w* \*\w*\.Server\) error {\n`)
|
| +
|
| +func getPkgs() []Pkg {
|
| + cmd := exec.Command("goapp", "list", "-json", filepath.Join(*servicePackagesBase+"..."))
|
| + d, err := cmd.Output()
|
| + boom(err)
|
| +
|
| + d = []byte("[" + strings.Replace(string(d), "}\n{", "},{", -1) + "]")
|
| +
|
| + js := interface{}(nil)
|
| + boom(json.Unmarshal(d, &js))
|
| +
|
| + ret := []Pkg{}
|
| +
|
| + type pkgOk struct {
|
| + p Pkg
|
| + ok bool
|
| + }
|
| + pkgs := make(chan pkgOk)
|
| + wg := sync.WaitGroup{}
|
| +
|
| + for _, m := range js.([]interface{}) {
|
| + pkg := Pkg{
|
| + jsutil.Get(m, "ImportPath").(string),
|
| + jsutil.Get(m, "Dir").(string),
|
| + }
|
| + wg.Add(1)
|
| + go func() {
|
| + defer wg.Done()
|
| + paths, err := ioutil.ReadDir(pkg.Pth)
|
| + boom(err)
|
| +
|
| + for _, f := range paths {
|
| + if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
|
| + data, err := ioutil.ReadFile(filepath.Join(pkg.Pth, f.Name()))
|
| + boom(err)
|
| + if reRegisterEndpointsService.Match(data) {
|
| + pkgs <- pkgOk{pkg, true}
|
| + return
|
| + }
|
| + }
|
| + }
|
| + pkgs <- pkgOk{pkg, false}
|
| + }()
|
| + }
|
| + go func() {
|
| + wg.Wait()
|
| + close(pkgs)
|
| + }()
|
| +
|
| + for p := range pkgs {
|
| + if !p.ok {
|
| + if *verbose {
|
| + fmt.Println("skipping package", p.p.Imp, "(it doesn't impliment RegisterEndpointsService?)")
|
| + }
|
| + } else {
|
| + fmt.Println("including package", p.p.Imp)
|
| + ret = append(ret, p.p)
|
| + }
|
| + }
|
| +
|
| + return ret
|
| +}
|
| +
|
| +func writeFiles(dir string, pkgs []Pkg) {
|
| + input := &templInput{pkgs, time.Now()}
|
| + for _, ext := range []string{"go", "yaml"} {
|
| + buf := &bytes.Buffer{}
|
| + boom(templ.ExecuteTemplate(buf, ext, input))
|
| + boom(ioutil.WriteFile(filepath.Join(dir, "app."+ext), buf.Bytes(), 0666))
|
| + }
|
| +}
|
| +
|
| +func startServer(dir string) func() {
|
| + args := []string{"serve"}
|
| + if *clearDatastore {
|
| + args = append(args, "-clear_datastore")
|
| + }
|
| + args = append(args, dir)
|
| + server := exec.Command("goapp", args...)
|
| + server.SysProcAttr = serverStartParams
|
| + server.Stdout = os.Stdout
|
| + server.Stderr = os.Stderr
|
| + boom(server.Start())
|
| +
|
| + return func() {
|
| + server.Process.Signal(os.Interrupt)
|
| + server.Wait()
|
| + }
|
| +}
|
| +
|
| +func main() {
|
| + _, err := exec.LookPath("goapp")
|
| + if err != nil {
|
| + panic("goapp must be on your path")
|
| + }
|
| +
|
| + flag.Parse()
|
| + pkgs := getPkgs()
|
| +
|
| + dir, err := ioutil.TempDir("", "epservice_gen")
|
| + boom(err)
|
| + prefix := "LEAKING"
|
| + if !*leak {
|
| + prefix = "generating"
|
| + defer os.RemoveAll(dir)
|
| + }
|
| + fmt.Println(prefix, "files in:", dir)
|
| +
|
| + writeFiles(dir, pkgs)
|
| +
|
| + intC := make(chan os.Signal, 1)
|
| + signal.Notify(intC, os.Interrupt, os.Kill)
|
| +
|
| + stop := startServer(dir)
|
| + defer stop()
|
| + <-intC
|
| +}
|
|
|