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..8149b15238bba3edff8f50a642a64527d61be03e |
--- /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) |
dnj
2015/06/09 17:17:35
Use "defer stop()".
iannucci
2015/06/09 20:51:07
Done.
|
+ <-intC |
+ stop() |
+} |