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 }