Index: go/src/infra/gae/epclient/epclient.go |
diff --git a/go/src/infra/gae/epclient/epclient.go b/go/src/infra/gae/epclient/epclient.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6c55da03931fe97d593d80edc92c67861424b79e |
--- /dev/null |
+++ b/go/src/infra/gae/epclient/epclient.go |
@@ -0,0 +1,206 @@ |
+// 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. |
+ |
+// The following comment allows `go generate` to correctly generate clients |
+// for all endpoints services located in the "infra/gae/epservice" package. |
+//go:generate goapp run epclient.go |
+ |
+// EPClient will auto-generate endpoints clients from Google Cloud Endpoint |
+// service definitions. The tool assumes that: |
+// * all go endpoint service definitions occur in a package which has |
+// a public method `RegisterEndpointsService(*endpoints.Server) error`, and |
+// that calling this method will register the endpoints service with the |
+// provided server or return an error. |
+// * goapp exists in PATH (and understands all the import paths in the |
+// defined services) |
+// * google-api-go-generator exists in PATH. |
dnj
2015/06/08 16:43:18
Could you "enforce" this by adding it as a Go depe
iannucci
2015/06/09 00:01:54
It's a 'main' package, so I can't add it as an imp
|
+package main |
+ |
+import ( |
+ "bytes" |
+ "encoding/json" |
+ "flag" |
+ "fmt" |
+ "io/ioutil" |
+ "os" |
+ "os/exec" |
+ "path/filepath" |
+ "strings" |
+ "text/template" |
+ "time" |
+ |
+ "infra/libs/jsutil" |
+) |
+ |
+var ( |
+ servicePackagesBase = flag.String("pkgs", "infra/gae/epservice", |
+ "The base Go package to walk to find service definitions.") |
+ outDir = flag.String("outdir", ".", |
+ "The directory to generate the client libraries in.") |
+ runOnly = flag.Bool("run", false, |
+ "If set to true, will run the server and wait, without generating any clients.") |
+ clearDatastore = flag.Bool("clear_datastore", false, |
+ "If set to true, will clear the datastore when in 'run' mode.") |
+) |
+ |
+var serviceScript = ` |
+package main |
dnj
2015/06/08 16:43:18
Add "generated" comment, info, timestamp.
iannucci
2015/06/09 00:01:54
Done.
|
+ |
+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) |
+} |
+` |
+ |
+const appYaml = ` |
+application: epclient-tmp-app |
dnj
2015/06/08 16:43:18
Add "generated" comment, info, timestamp.
iannucci
2015/06/09 00:01:54
Done.
|
+version: nope |
+runtime: go |
+api_version: go1 |
+ |
+handlers: |
+- url: /.* |
+ script: _go_app |
+` |
+ |
+var temp = template.Must(template.New("service").Parse(serviceScript)) |
+ |
+// 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) |
+ } |
+} |
+ |
+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{} |
+ |
+ for _, m := range js.([]interface{}) { |
+ ret = append(ret, Pkg{ |
+ jsutil.Get(m, "ImportPath").(string), |
+ jsutil.Get(m, "Dir").(string), |
+ }) |
+ } |
+ |
+ return ret |
+} |
+ |
+func writeFiles(dir string, pkgs []Pkg) { |
+ buf := &bytes.Buffer{} |
+ if err := temp.Execute(buf, struct{ Pkgs []Pkg }{pkgs}); err != nil { |
+ panic(err) |
+ } |
+ |
+ if err := ioutil.WriteFile(filepath.Join(dir, "app.go"), buf.Bytes(), 0666); err != nil { |
+ panic(err) |
+ } |
+ |
+ if err := ioutil.WriteFile(filepath.Join(dir, "app.yaml"), []byte(appYaml), 0666); err != nil { |
+ panic(err) |
+ } |
+} |
+ |
+func parseFlags() { |
+ err := error(nil) |
dnj
2015/06/08 16:43:17
I generally prefer "var err error" *shrug* Up to y
iannucci
2015/06/09 00:01:53
me too... I was getting bikeshedding elsewhere on
|
+ flag.Parse() |
+ if *outDir == "" { |
+ if *outDir, err = os.Getwd(); err != nil { |
+ panic(err) |
+ } |
+ } |
+ if *outDir, err = filepath.Abs(*outDir); err != nil { |
+ panic(err) |
+ } |
+} |
+ |
+func startServer(dir string) (func(), func()) { |
+ args := []string{"serve"} |
+ if *clearDatastore { |
+ args = append(args, "-clear_datastore") |
+ } |
+ args = append(args, dir) |
+ server := exec.Command("goapp", args...) |
+ server.Stdout = os.Stdout |
+ server.Stderr = os.Stderr |
+ if err := server.Start(); err != nil { |
+ panic(err) |
+ } |
+ time.Sleep(time.Second) // yeah yeah, sue me. |
dnj
2015/06/08 16:43:18
Since you know the discovery service URL, you coul
iannucci
2015/06/09 00:01:54
Done.
|
+ fmt.Println("Discovery service up") |
+ |
+ wait := func() { server.Wait() } |
+ stop := func() { |
+ server.Process.Signal(os.Interrupt) |
+ wait() |
+ } |
+ return stop, wait |
+} |
+ |
+func generate() { |
+ gencmd := exec.Command("google-api-go-generator", "-discoveryurl", "http://localhost:8080/_ah/api/discovery/v1/apis", "-gendir", *outDir, "-cache=false") |
+ gencmd.Stdout = os.Stdout |
+ gencmd.Stderr = os.Stderr |
+ if err := gencmd.Run(); err != nil { |
+ panic(err) |
+ } |
+} |
+ |
+func main() { |
+ parseFlags() |
+ |
dnj
2015/06/08 16:43:18
Test that "google-api-go-generator" and "goapp" ar
iannucci
2015/06/09 00:01:53
Done.
|
+ pkgs := getPkgs() |
+ |
+ dir, err := ioutil.TempDir("", "epclient_gen") |
+ if err != nil { |
+ panic(err) |
+ } |
+ defer os.RemoveAll(dir) |
+ |
+ writeFiles(dir, pkgs) |
+ |
+ stop, wait := startServer(dir) |
+ defer stop() |
+ |
+ if *runOnly { |
+ wait() |
+ } else { |
+ generate() |
+ } |
+ |
+ // inject DoWithRetries methods. |
+} |