Mercurial > gemma
comparison pkg/geoserver/boot.go @ 868:aa8f30c1ed27 geo-style
Moved GeoServer configuration to own package.
author | Sascha L. Teichmann <teichmann@intevation.de> |
---|---|
date | Sat, 29 Sep 2018 22:34:24 +0200 |
parents | cmd/gemma/geoserver.go@f827dc4f3e95 |
children | da526b58c9c4 |
comparison
equal
deleted
inserted
replaced
867:848c44e01060 | 868:aa8f30c1ed27 |
---|---|
1 package geoserver | |
2 | |
3 import ( | |
4 "bytes" | |
5 "encoding/json" | |
6 "fmt" | |
7 "log" | |
8 "net" | |
9 "net/http" | |
10 "net/url" | |
11 "strings" | |
12 "time" | |
13 | |
14 "gemma.intevation.de/gemma/pkg/config" | |
15 "gemma.intevation.de/gemma/pkg/models" | |
16 ) | |
17 | |
18 const ( | |
19 workspaceName = "gemma" | |
20 datastoreName = "gemma" | |
21 databaseScheme = "waterway" | |
22 databaseType = "postgis" | |
23 ) | |
24 | |
25 const ( | |
26 startupSQL = `SELECT public.setrole('${user,'||encode('waterway_user', 'hex')||'}')` | |
27 closeupSQL = `RESET ROLE` | |
28 ) | |
29 | |
30 func basicAuth(user, password string) func(req *http.Request) { | |
31 return func(req *http.Request) { | |
32 req.SetBasicAuth(user, password) | |
33 } | |
34 } | |
35 | |
36 func asJSON(req *http.Request) { | |
37 req.Header.Set("Content-Type", "application/json") | |
38 } | |
39 | |
40 func ensureWorkspace() error { | |
41 var ( | |
42 url = config.GeoServerURL() | |
43 user = config.GeoServerUser() | |
44 password = config.GeoServerPassword() | |
45 auth = basicAuth(user, password) | |
46 ) | |
47 | |
48 // Probe workspace. | |
49 req, err := http.NewRequest( | |
50 http.MethodGet, | |
51 url+"/rest/workspaces/"+workspaceName+".json", | |
52 nil) | |
53 if err != nil { | |
54 return err | |
55 } | |
56 auth(req) | |
57 resp, err := http.DefaultClient.Do(req) | |
58 if err != nil { | |
59 return err | |
60 } | |
61 | |
62 if resp.StatusCode != http.StatusNotFound { | |
63 log.Println("info: workspace " + workspaceName + " already exists.") | |
64 return nil | |
65 } | |
66 | |
67 // Create workspace | |
68 | |
69 log.Println("info: creating workspace " + workspaceName) | |
70 | |
71 const createJSON = `{"workspace":{"name":"` + workspaceName + `"}}` | |
72 | |
73 req, err = http.NewRequest( | |
74 http.MethodPost, | |
75 url+"/rest/workspaces", | |
76 strings.NewReader(createJSON)) | |
77 if err != nil { | |
78 return err | |
79 } | |
80 auth(req) | |
81 asJSON(req) | |
82 if resp, err = http.DefaultClient.Do(req); err != nil { | |
83 return err | |
84 } | |
85 | |
86 if resp.StatusCode != http.StatusCreated { | |
87 err = fmt.Errorf("Status code '%s' (%d)", | |
88 http.StatusText(resp.StatusCode), | |
89 resp.StatusCode) | |
90 } | |
91 | |
92 return err | |
93 } | |
94 | |
95 func ensureDataStore() error { | |
96 var ( | |
97 url = config.GeoServerURL() | |
98 user = config.GeoServerUser() | |
99 password = config.GeoServerPassword() | |
100 auth = basicAuth(user, password) | |
101 ) | |
102 | |
103 // Probe datastore. | |
104 req, err := http.NewRequest( | |
105 http.MethodGet, | |
106 url+"/rest/workspaces/"+workspaceName+"/datastores/"+datastoreName+".json", | |
107 nil) | |
108 if err != nil { | |
109 return err | |
110 } | |
111 auth(req) | |
112 resp, err := http.DefaultClient.Do(req) | |
113 if err != nil { | |
114 return err | |
115 } | |
116 | |
117 if resp.StatusCode != http.StatusNotFound { | |
118 log.Println("info: datastore " + datastoreName + " already exists.") | |
119 return nil | |
120 } | |
121 | |
122 // Create datastore. | |
123 log.Println("info: creating datastore " + datastoreName) | |
124 | |
125 type entry struct { | |
126 Key interface{} `json:"@key"` | |
127 Value interface{} `json:"$"` | |
128 } | |
129 | |
130 // Create datastore. | |
131 ds := map[string]interface{}{ | |
132 "dataStore": map[string]interface{}{ | |
133 "name": datastoreName, | |
134 "connectionParameters": map[string]interface{}{ | |
135 "entry": []entry{ | |
136 {"host", config.DBHost()}, | |
137 {"port", config.DBPort()}, | |
138 {"database", config.DBName()}, | |
139 {"schema", databaseScheme}, | |
140 {"user", config.DBUser()}, | |
141 {"passwd", config.DBPassword()}, | |
142 {"dbtype", databaseType}, | |
143 {"Session startup SQL", startupSQL}, | |
144 {"Session close-up SQL", closeupSQL}, | |
145 }, | |
146 }, | |
147 }, | |
148 } | |
149 var out bytes.Buffer | |
150 enc := json.NewEncoder(&out) | |
151 if err := enc.Encode(&ds); err != nil { | |
152 return err | |
153 } | |
154 | |
155 req, err = http.NewRequest( | |
156 http.MethodPost, | |
157 url+"/rest/workspaces/"+workspaceName+"/datastores", | |
158 bytes.NewReader(out.Bytes())) | |
159 if err != nil { | |
160 return err | |
161 } | |
162 auth(req) | |
163 asJSON(req) | |
164 resp, err = http.DefaultClient.Do(req) | |
165 if err != nil { | |
166 return err | |
167 } | |
168 | |
169 if resp.StatusCode != http.StatusCreated { | |
170 err = fmt.Errorf("Status code '%s' (%d)", | |
171 http.StatusText(resp.StatusCode), | |
172 resp.StatusCode) | |
173 } | |
174 | |
175 return err | |
176 } | |
177 | |
178 func ensureFeatures() error { | |
179 var ( | |
180 url = config.GeoServerURL() | |
181 user = config.GeoServerUser() | |
182 password = config.GeoServerPassword() | |
183 auth = basicAuth(user, password) | |
184 ) | |
185 | |
186 tables := models.InternalServices.Filter(models.IntWFS) | |
187 if len(tables) == 0 { | |
188 log.Println("info: no tables to publish") | |
189 return nil | |
190 } | |
191 | |
192 log.Printf("info: number of tables to publish %d\n", len(tables)) | |
193 | |
194 var features struct { | |
195 FeatureTypes struct { | |
196 FeatureType []struct { | |
197 Name string `json:"name"` | |
198 } `json:"featureType"` | |
199 } `json:"featureTypes"` | |
200 } | |
201 | |
202 hasFeature := func(name string) bool { | |
203 for _, ft := range features.FeatureTypes.FeatureType { | |
204 if ft.Name == name { | |
205 return true | |
206 } | |
207 } | |
208 return false | |
209 } | |
210 | |
211 // Fetch all featuretypes. | |
212 req, err := http.NewRequest( | |
213 http.MethodGet, | |
214 url+"/rest/workspaces/"+workspaceName+ | |
215 "/datastores/"+datastoreName+ | |
216 "/featuretypes.json", | |
217 nil) | |
218 if err != nil { | |
219 return err | |
220 } | |
221 auth(req) | |
222 resp, err := http.DefaultClient.Do(req) | |
223 if err != nil { | |
224 return err | |
225 } | |
226 | |
227 err = json.NewDecoder(resp.Body).Decode(&features) | |
228 resp.Body.Close() | |
229 if err != nil { | |
230 // XXX: Quirk in the JSON return by GeoServer: | |
231 // If there are no features in the datastore | |
232 // featureType deserializes to an empty string "" | |
233 // instead of an empty array *palmface*. | |
234 // So assume there no features. | |
235 hasFeature = func(string) bool { return false } | |
236 } | |
237 | |
238 for i := range tables { | |
239 table := tables[i].Name | |
240 | |
241 if hasFeature(table) { | |
242 log.Printf("info: featuretype %s already exists.\n", table) | |
243 continue | |
244 } | |
245 | |
246 // Create featuretype. | |
247 log.Printf("info: creating featuretype %s.\n", table) | |
248 | |
249 // Create featuretype | |
250 ft := map[string]interface{}{ | |
251 "featureType": map[string]interface{}{ | |
252 "name": table, | |
253 "nativeName": table, | |
254 "title": table, | |
255 }, | |
256 } | |
257 | |
258 var out bytes.Buffer | |
259 enc := json.NewEncoder(&out) | |
260 if err := enc.Encode(&ft); err != nil { | |
261 return err | |
262 } | |
263 | |
264 req, err := http.NewRequest( | |
265 http.MethodPost, | |
266 url+"/rest/workspaces/"+workspaceName+ | |
267 "/datastores/"+datastoreName+ | |
268 "/featuretypes", | |
269 bytes.NewReader(out.Bytes())) | |
270 if err != nil { | |
271 return err | |
272 } | |
273 auth(req) | |
274 asJSON(req) | |
275 resp, err := http.DefaultClient.Do(req) | |
276 if err != nil { | |
277 return err | |
278 } | |
279 | |
280 if resp.StatusCode != http.StatusCreated { | |
281 return fmt.Errorf("Status code '%s' (%d)", | |
282 http.StatusText(resp.StatusCode), | |
283 resp.StatusCode) | |
284 } | |
285 } | |
286 | |
287 return nil | |
288 } | |
289 | |
290 func prepareGeoServer() error { | |
291 | |
292 if config.DBUser() == "" { | |
293 log.Println("info: Need metamorphic db user to configure GeoServer") | |
294 return nil | |
295 } | |
296 | |
297 if config.GeoServerURL() == "" { | |
298 log.Println("info: No tables to publish on GeoServer") | |
299 return nil | |
300 } | |
301 | |
302 if err := ensureWorkspace(); err != nil { | |
303 return err | |
304 } | |
305 | |
306 if err := ensureDataStore(); err != nil { | |
307 return err | |
308 } | |
309 | |
310 // TODO: Styles | |
311 | |
312 return ensureFeatures() | |
313 } | |
314 | |
315 func ConfigureBoot() { | |
316 log.Println("Configure GeoServer...") | |
317 const maxTries = 10 | |
318 const sleep = time.Second * 5 | |
319 | |
320 for try := 1; try <= maxTries; try++ { | |
321 err := prepareGeoServer() | |
322 if err == nil { | |
323 break | |
324 } | |
325 if try < maxTries { | |
326 if uerr, ok := err.(*url.Error); ok { | |
327 if oerr, ok := uerr.Err.(*net.OpError); ok && oerr.Op == "dial" { | |
328 log.Printf("Failed attempt %d of %d to configure GeoServer. "+ | |
329 "Will try again in %s...\n", try, maxTries, sleep) | |
330 time.Sleep(sleep) | |
331 continue | |
332 } | |
333 } | |
334 } | |
335 log.Printf("warn: configure GeoServer failed: %v\n", err) | |
336 break | |
337 } | |
338 } |