Mercurial > gemma
view pkg/geoserver/boot.go @ 877:254cd247826d geo-style
The XSLT stuff is not need (Puh!).
author | Sascha L. Teichmann <teichmann@intevation.de> |
---|---|
date | Sun, 30 Sep 2018 20:09:02 +0200 |
parents | 8b9bd9ccdd93 |
children | 495fc3264265 |
line wrap: on
line source
package geoserver import ( "bytes" "encoding/json" "encoding/xml" "fmt" "io" "log" "net" "net/http" "net/url" "strings" "time" "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/models" ) const ( workspaceName = "gemma" datastoreName = "gemma" databaseScheme = "waterway" databaseType = "postgis" ) const ( startupSQL = `SELECT public.setrole('${user,'||encode('waterway_user', 'hex')||'}')` closeupSQL = `RESET ROLE` ) func basicAuth(user, password string) func(req *http.Request) { return func(req *http.Request) { req.SetBasicAuth(user, password) } } func asJSON(req *http.Request) { req.Header.Set("Content-Type", "application/json") } func asContentType(req *http.Request, contentType string) { req.Header.Set("Content-Type", contentType) } func ensureWorkspace() error { var ( geoURL = config.GeoServerURL() user = config.GeoServerUser() password = config.GeoServerPassword() auth = basicAuth(user, password) ) // Probe workspace. req, err := http.NewRequest( http.MethodGet, geoURL+"/rest/workspaces/"+workspaceName+".json", nil) if err != nil { return err } auth(req) resp, err := http.DefaultClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusNotFound { log.Println("info: workspace " + workspaceName + " already exists.") return nil } // Create workspace log.Println("info: creating workspace " + workspaceName) const createJSON = `{"workspace":{"name":"` + workspaceName + `"}}` req, err = http.NewRequest( http.MethodPost, geoURL+"/rest/workspaces", strings.NewReader(createJSON)) if err != nil { return err } auth(req) asJSON(req) if resp, err = http.DefaultClient.Do(req); err != nil { return err } if resp.StatusCode != http.StatusCreated { err = fmt.Errorf("Status code '%s' (%d)", http.StatusText(resp.StatusCode), resp.StatusCode) } return err } func ensureDataStore() error { var ( geoURL = config.GeoServerURL() user = config.GeoServerUser() password = config.GeoServerPassword() auth = basicAuth(user, password) ) // Probe datastore. req, err := http.NewRequest( http.MethodGet, geoURL+"/rest/workspaces/"+workspaceName+"/datastores/"+datastoreName+".json", nil) if err != nil { return err } auth(req) resp, err := http.DefaultClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusNotFound { log.Println("info: datastore " + datastoreName + " already exists.") return nil } // Create datastore. log.Println("info: creating datastore " + datastoreName) type entry struct { Key interface{} `json:"@key"` Value interface{} `json:"$"` } // Create datastore. ds := map[string]interface{}{ "dataStore": map[string]interface{}{ "name": datastoreName, "connectionParameters": map[string]interface{}{ "entry": []entry{ {"host", config.DBHost()}, {"port", config.DBPort()}, {"database", config.DBName()}, {"schema", databaseScheme}, {"user", config.DBUser()}, {"passwd", config.DBPassword()}, {"dbtype", databaseType}, {"Session startup SQL", startupSQL}, {"Session close-up SQL", closeupSQL}, }, }, }, } var out bytes.Buffer enc := json.NewEncoder(&out) if err := enc.Encode(&ds); err != nil { return err } req, err = http.NewRequest( http.MethodPost, geoURL+"/rest/workspaces/"+workspaceName+"/datastores", bytes.NewReader(out.Bytes())) if err != nil { return err } auth(req) asJSON(req) resp, err = http.DefaultClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusCreated { err = fmt.Errorf("Status code '%s' (%d)", http.StatusText(resp.StatusCode), resp.StatusCode) } return err } func ensureFeatures() error { var ( geoURL = config.GeoServerURL() user = config.GeoServerUser() password = config.GeoServerPassword() auth = basicAuth(user, password) ) tables := models.InternalServices.Filter(models.IntWFS) if len(tables) == 0 { log.Println("info: no tables to publish") return nil } log.Printf("info: number of tables to publish %d\n", len(tables)) var features struct { FeatureTypes struct { FeatureType []struct { Name string `json:"name"` } `json:"featureType"` } `json:"featureTypes"` } hasFeature := func(name string) bool { for _, ft := range features.FeatureTypes.FeatureType { if ft.Name == name { return true } } return false } // Fetch all featuretypes. req, err := http.NewRequest( http.MethodGet, geoURL+"/rest/workspaces/"+workspaceName+ "/datastores/"+datastoreName+ "/featuretypes.json", nil) if err != nil { return err } auth(req) resp, err := http.DefaultClient.Do(req) if err != nil { return err } err = json.NewDecoder(resp.Body).Decode(&features) resp.Body.Close() if err != nil { // XXX: Quirk in the JSON return by GeoServer: // If there are no features in the datastore // featureType deserializes to an empty string "" // instead of an empty array *palmface*. // So assume there no features. hasFeature = func(string) bool { return false } } for i := range tables { table := tables[i].Name if hasFeature(table) { log.Printf("info: featuretype %s already exists.\n", table) continue } // Create featuretype. log.Printf("info: creating featuretype %s.\n", table) // Create featuretype ft := map[string]interface{}{ "featureType": map[string]interface{}{ "name": table, "nativeName": table, "title": table, }, } var out bytes.Buffer enc := json.NewEncoder(&out) if err := enc.Encode(&ft); err != nil { return err } req, err := http.NewRequest( http.MethodPost, geoURL+"/rest/workspaces/"+workspaceName+ "/datastores/"+datastoreName+ "/featuretypes", bytes.NewReader(out.Bytes())) if err != nil { return err } auth(req) asJSON(req) resp, err := http.DefaultClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusCreated { return fmt.Errorf("Status code '%s' (%d)", http.StatusText(resp.StatusCode), resp.StatusCode) } } return nil } func deleteWorkspace() error { // Should we delete our workspace first? if !config.GeoServerClean() { return nil } log.Println("info: delete workspace " + workspaceName) var ( geoURL = config.GeoServerURL() user = config.GeoServerUser() password = config.GeoServerPassword() auth = basicAuth(user, password) ) req, err := http.NewRequest( http.MethodDelete, geoURL+"/rest/workspaces/"+workspaceName+"?recurse=true", nil) if err != nil { return err } auth(req) _, err = http.DefaultClient.Do(req) return err } func ensureStyles() error { log.Println("info: creating styles") var ( geoURL = config.GeoServerURL() user = config.GeoServerUser() password = config.GeoServerPassword() auth = basicAuth(user, password) ) type styles struct { Styles struct { Style []struct { Name string `json:"name"` } `json:"style"` } `json:"styles"` } var stls styles hasStyle := func(name string) bool { for i := range stls.Styles.Style { if stls.Styles.Style[i].Name == name { return true } } return false } req, err := http.NewRequest( http.MethodGet, geoURL+"/rest/workspaces/"+workspaceName+"/styles.json", nil) if err != nil { return err } auth(req) resp, err := http.DefaultClient.Do(req) if err != nil { return err } // Fetch all styles if err := json.NewDecoder(resp.Body).Decode(&stls); err != nil { // XXX: Same quirk as with featuretypes. } resp.Body.Close() entries := models.InternalServices.Filter( models.IntAnd(models.IntWMS, models.IntWithStyle)) for i := range entries { if hasStyle(entries[i].Name) { log.Printf("already has style for %s\n", entries[i].Name) continue } log.Printf("creating style %s\n", entries[i].Name) req, err := http.NewRequest( http.MethodPost, geoURL+"/rest/workspaces/"+workspaceName+"/styles?name="+ url.QueryEscape(entries[i].Name), strings.NewReader(entries[i].Style.String)) if err != nil { return err } auth(req) if isSymbologyEncoding(entries[i].Style.String) { asContentType(req, "application/vnd.ogc.se+xml") } else { asContentType(req, "application/vnd.ogc.sld+xml") } resp, err := http.DefaultClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusCreated { return fmt.Errorf("cannot create style %s (%s)", entries[i].Name, http.StatusText(resp.StatusCode)) } //log.Printf("%s\n", entries[i].Style.String) // TODO: Connect with feature. } return nil } // isSymbologyEncoding tries to figure out if its plain SLD or SE. func isSymbologyEncoding(data string) bool { decoder := xml.NewDecoder(strings.NewReader(data)) for { tok, err := decoder.Token() switch { case tok == nil && err == io.EOF: return false case err != nil: log.Printf("warn: invalid XML: %v\n", err) return false } if t, ok := tok.(xml.StartElement); ok && t.Name.Space == "http://www.opengis.net/se" { return true } } } func prepareGeoServer() error { if config.DBUser() == "" { log.Println("info: Need metamorphic db user to configure GeoServer") return nil } if config.GeoServerURL() == "" { log.Println("info: No URL to GeoServer configured") return nil } for _, ensure := range []func() error{ deleteWorkspace, ensureWorkspace, ensureDataStore, ensureFeatures, ensureStyles, } { if err := ensure(); err != nil { return err } } return nil } func ConfigureBoot() { log.Println("Configure GeoServer...") const maxTries = 10 const sleep = time.Second * 5 for try := 1; try <= maxTries; try++ { err := prepareGeoServer() if err == nil { break } if try < maxTries { if uerr, ok := err.(*url.Error); ok { if oerr, ok := uerr.Err.(*net.OpError); ok && oerr.Op == "dial" { log.Printf("Failed attempt %d of %d to configure GeoServer. "+ "Will try again in %s...\n", try, maxTries, sleep) time.Sleep(sleep) continue } } } log.Printf("warn: configure GeoServer failed: %v\n", err) break } }