OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // EPService will run a local appengine service with all of the endpoint |
| 6 // services under this directory. The tool assumes that: |
| 7 // * goapp exists in PATH (and understands all the import paths in the |
| 8 // defined services) |
| 9 package main |
| 10 |
| 11 import ( |
| 12 "bytes" |
| 13 "encoding/json" |
| 14 "flag" |
| 15 "fmt" |
| 16 "io/ioutil" |
| 17 "os" |
| 18 "os/exec" |
| 19 "os/signal" |
| 20 "path/filepath" |
| 21 "regexp" |
| 22 "strings" |
| 23 "sync" |
| 24 "text/template" |
| 25 "time" |
| 26 |
| 27 "infra/libs/jsutil" |
| 28 ) |
| 29 |
| 30 var ( |
| 31 clearDatastore = flag.Bool("clear_datastore", false, "if set, clear
the datastore.") |
| 32 leak = flag.Bool("leak", false, "if set, leak the tempora
ry directory.") |
| 33 verbose = flag.Bool("verbose", false, "if set, print skipped
packages.") |
| 34 servicePackagesBase = flag.String("base", "infra", |
| 35 "base Go package to walk to find service definitions.") |
| 36 ) |
| 37 |
| 38 var serviceScript = `{{define "go"}} |
| 39 // DO NOT EDIT |
| 40 // Auto-generated by infra/gae/epservice |
| 41 // {{.Timestamp}} |
| 42 |
| 43 package main |
| 44 |
| 45 import ( |
| 46 "fmt" |
| 47 |
| 48 {{range $idx, $pkg := .Pkgs}} |
| 49 pkg{{$idx}} "{{$pkg.Imp}}"{{end}} |
| 50 |
| 51 "github.com/GoogleCloudPlatform/go-endpoints/endpoints" |
| 52 ) |
| 53 |
| 54 func init() { |
| 55 var err error |
| 56 server := endpoints.NewServer("") |
| 57 |
| 58 {{range $idx, $pkg := .Pkgs }} |
| 59 err = pkg{{$idx}}.RegisterEndpointsService(server) |
| 60 if err != nil { |
| 61 panic(fmt.Errorf("Error while registering service {{$pkg}}: %s",
err)) |
| 62 } |
| 63 {{end}} |
| 64 |
| 65 server.HandleHTTP(nil) |
| 66 } |
| 67 {{end}} |
| 68 ` |
| 69 |
| 70 const appYaml = `{{define "yaml"}} |
| 71 # DO NOT EDIT |
| 72 # Auto-generated by infra/gae/epservice |
| 73 # {{.Timestamp}} |
| 74 |
| 75 application: epclient-tmp-app |
| 76 version: nope |
| 77 runtime: go |
| 78 api_version: go1 |
| 79 |
| 80 handlers: |
| 81 - url: /.* |
| 82 script: _go_app |
| 83 {{end}} |
| 84 ` |
| 85 |
| 86 var templ = template.New("service") |
| 87 |
| 88 func init() { |
| 89 template.Must(templ.Parse(serviceScript)) |
| 90 template.Must(templ.Parse(appYaml)) |
| 91 } |
| 92 |
| 93 type templInput struct { |
| 94 Pkgs []Pkg |
| 95 Timestamp time.Time |
| 96 } |
| 97 |
| 98 // Pkg holds the import and real filesystem paths of an endpoint service |
| 99 // package. It's exported merely for reflection purposes, since it's used |
| 100 // by text/template. |
| 101 type Pkg struct { |
| 102 Imp string |
| 103 Pth string |
| 104 } |
| 105 |
| 106 func boom(err error) { |
| 107 if err != nil { |
| 108 panic(err) |
| 109 } |
| 110 } |
| 111 |
| 112 // NOTE: if you format your RegisterEndpointsService implementation like a bozo, |
| 113 // then this won't match. Don't do that. |
| 114 var reRegisterEndpointsService = regexp.MustCompile( |
| 115 `\nfunc RegisterEndpointsService\(\w* \*\w*\.Server\) error {\n`) |
| 116 |
| 117 func getPkgs() []Pkg { |
| 118 cmd := exec.Command("goapp", "list", "-json", filepath.Join(*servicePack
agesBase+"...")) |
| 119 d, err := cmd.Output() |
| 120 boom(err) |
| 121 |
| 122 d = []byte("[" + strings.Replace(string(d), "}\n{", "},{", -1) + "]") |
| 123 |
| 124 js := interface{}(nil) |
| 125 boom(json.Unmarshal(d, &js)) |
| 126 |
| 127 ret := []Pkg{} |
| 128 |
| 129 type pkgOk struct { |
| 130 p Pkg |
| 131 ok bool |
| 132 } |
| 133 pkgs := make(chan pkgOk) |
| 134 wg := sync.WaitGroup{} |
| 135 |
| 136 for _, m := range js.([]interface{}) { |
| 137 pkg := Pkg{ |
| 138 jsutil.Get(m, "ImportPath").(string), |
| 139 jsutil.Get(m, "Dir").(string), |
| 140 } |
| 141 wg.Add(1) |
| 142 go func() { |
| 143 defer wg.Done() |
| 144 paths, err := ioutil.ReadDir(pkg.Pth) |
| 145 boom(err) |
| 146 |
| 147 for _, f := range paths { |
| 148 if !f.IsDir() && strings.HasSuffix(f.Name(), ".g
o") { |
| 149 data, err := ioutil.ReadFile(filepath.Jo
in(pkg.Pth, f.Name())) |
| 150 boom(err) |
| 151 if reRegisterEndpointsService.Match(data
) { |
| 152 pkgs <- pkgOk{pkg, true} |
| 153 return |
| 154 } |
| 155 } |
| 156 } |
| 157 pkgs <- pkgOk{pkg, false} |
| 158 }() |
| 159 } |
| 160 go func() { |
| 161 wg.Wait() |
| 162 close(pkgs) |
| 163 }() |
| 164 |
| 165 for p := range pkgs { |
| 166 if !p.ok { |
| 167 if *verbose { |
| 168 fmt.Println("skipping package", p.p.Imp, "(it do
esn't impliment RegisterEndpointsService?)") |
| 169 } |
| 170 } else { |
| 171 fmt.Println("including package", p.p.Imp) |
| 172 ret = append(ret, p.p) |
| 173 } |
| 174 } |
| 175 |
| 176 return ret |
| 177 } |
| 178 |
| 179 func writeFiles(dir string, pkgs []Pkg) { |
| 180 input := &templInput{pkgs, time.Now()} |
| 181 for _, ext := range []string{"go", "yaml"} { |
| 182 buf := &bytes.Buffer{} |
| 183 boom(templ.ExecuteTemplate(buf, ext, input)) |
| 184 boom(ioutil.WriteFile(filepath.Join(dir, "app."+ext), buf.Bytes(
), 0666)) |
| 185 } |
| 186 } |
| 187 |
| 188 func startServer(dir string) func() { |
| 189 args := []string{"serve"} |
| 190 if *clearDatastore { |
| 191 args = append(args, "-clear_datastore") |
| 192 } |
| 193 args = append(args, dir) |
| 194 server := exec.Command("goapp", args...) |
| 195 server.SysProcAttr = serverStartParams |
| 196 server.Stdout = os.Stdout |
| 197 server.Stderr = os.Stderr |
| 198 boom(server.Start()) |
| 199 |
| 200 return func() { |
| 201 server.Process.Signal(os.Interrupt) |
| 202 server.Wait() |
| 203 } |
| 204 } |
| 205 |
| 206 func main() { |
| 207 _, err := exec.LookPath("goapp") |
| 208 if err != nil { |
| 209 panic("goapp must be on your path") |
| 210 } |
| 211 |
| 212 flag.Parse() |
| 213 pkgs := getPkgs() |
| 214 |
| 215 dir, err := ioutil.TempDir("", "epservice_gen") |
| 216 boom(err) |
| 217 prefix := "LEAKING" |
| 218 if !*leak { |
| 219 prefix = "generating" |
| 220 defer os.RemoveAll(dir) |
| 221 } |
| 222 fmt.Println(prefix, "files in:", dir) |
| 223 |
| 224 writeFiles(dir, pkgs) |
| 225 |
| 226 intC := make(chan os.Signal, 1) |
| 227 signal.Notify(intC, os.Interrupt, os.Kill) |
| 228 |
| 229 stop := startServer(dir) |
| 230 defer stop() |
| 231 <-intC |
| 232 } |
OLD | NEW |