Mercurial > gemma
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/geoserver/boot.go Sat Sep 29 22:34:24 2018 +0200 @@ -0,0 +1,338 @@ +package geoserver + +import ( + "bytes" + "encoding/json" + "fmt" + "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 ensureWorkspace() error { + var ( + url = config.GeoServerURL() + user = config.GeoServerUser() + password = config.GeoServerPassword() + auth = basicAuth(user, password) + ) + + // Probe workspace. + req, err := http.NewRequest( + http.MethodGet, + url+"/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, + url+"/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 ( + url = config.GeoServerURL() + user = config.GeoServerUser() + password = config.GeoServerPassword() + auth = basicAuth(user, password) + ) + + // Probe datastore. + req, err := http.NewRequest( + http.MethodGet, + url+"/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, + url+"/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 ( + url = 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, + url+"/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, + url+"/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 prepareGeoServer() error { + + if config.DBUser() == "" { + log.Println("info: Need metamorphic db user to configure GeoServer") + return nil + } + + if config.GeoServerURL() == "" { + log.Println("info: No tables to publish on GeoServer") + return nil + } + + if err := ensureWorkspace(); err != nil { + return err + } + + if err := ensureDataStore(); err != nil { + return err + } + + // TODO: Styles + + return ensureFeatures() +} + +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 + } +}