# HG changeset patch # User Sascha L. Teichmann # Date 1538253353 -7200 # Node ID 42df1cabf410d3d993d1b7c224865712510289e0 # Parent 848c44e01060cf4acb07ed449e44a6e818cf3d72# Parent aa8f30c1ed2754c1a17bce0cd205ecced14fbc1b Merged package movement from geo-style back to default. diff -r 848c44e01060 -r 42df1cabf410 cmd/gemma/geoserver.go --- a/cmd/gemma/geoserver.go Sat Sep 29 22:24:31 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,310 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "log" - "net/http" - "strings" - - "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() -} diff -r 848c44e01060 -r 42df1cabf410 cmd/gemma/main.go --- a/cmd/gemma/main.go Sat Sep 29 22:24:31 2018 +0200 +++ b/cmd/gemma/main.go Sat Sep 29 22:35:53 2018 +0200 @@ -4,14 +4,11 @@ "context" "fmt" "log" - "net" "net/http" - "net/url" "os" "os/signal" "path/filepath" "syscall" - "time" "github.com/gorilla/mux" "github.com/rs/cors" @@ -20,6 +17,7 @@ "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/controllers" + "gemma.intevation.de/gemma/pkg/geoserver" ) func prepareSessionStore() { @@ -41,30 +39,7 @@ prepareSessionStore() // Do GeoServer setup in background. - go func() { - 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 - } - }() + go geoserver.ConfigureBoot() m := mux.NewRouter() controllers.BindRoutes(m) diff -r 848c44e01060 -r 42df1cabf410 pkg/geoserver/boot.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/geoserver/boot.go Sat Sep 29 22:35:53 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 + } +}