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(), 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.Stdout = os.Stdout | |
196 server.Stderr = os.Stderr | |
197 boom(server.Start()) | |
198 | |
199 wait := func() { server.Wait() } | |
200 stop := func() { | |
201 server.Process.Signal(os.Interrupt) | |
202 wait() | |
203 } | |
204 return stop, wait | |
205 } | |
206 | |
207 func main() { | |
208 _, err := exec.LookPath("goapp") | |
209 if err != nil { | |
210 panic("goapp must be on your path") | |
211 } | |
212 | |
213 flag.Parse() | |
214 pkgs := getPkgs() | |
215 | |
216 dir, err := ioutil.TempDir("", "epservice_gen") | |
217 boom(err) | |
218 prefix := "LEAKING" | |
219 if !*leak { | |
220 prefix = "generating" | |
221 defer os.RemoveAll(dir) | |
222 } | |
223 fmt.Println(prefix, "files in:", dir) | |
224 | |
225 writeFiles(dir, pkgs) | |
226 | |
227 stop, wait := startServer(dir) | |
228 defer wait() | |
229 | |
230 intC := make(chan os.Signal, 1) | |
dnj
2015/06/09 02:56:58
Way less cute of a channel name :(
| |
231 signal.Notify(intC, os.Interrupt, os.Kill) | |
232 sigC := make(chan struct{}) | |
233 go func() { | |
234 defer close(sigC) | |
235 <-intC | |
236 stop() | |
237 }() | |
238 | |
239 waitC := make(chan struct{}) | |
240 go func() { | |
241 defer close(waitC) | |
242 wait() | |
243 }() | |
244 | |
245 select { | |
246 case <-sigC: | |
247 case <-waitC: | |
248 } | |
249 time.Sleep(time.Second) | |
dnj
2015/06/09 02:56:58
Why is this necessary?
iannucci
2015/06/09 07:41:11
added it while I was debugging something else. The
| |
250 } | |
OLD | NEW |