Mercurial > gemma
changeset 390:6f77f33af651
merge
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Mon, 13 Aug 2018 16:21:38 +0200 |
parents | e7d5383bc358 (current diff) 56897dd9a2b2 (diff) |
children | 13c5fc6cbaea c57b952c60be |
files | docs/schnittstellen.txt |
diffstat | 7 files changed, 291 insertions(+), 68 deletions(-) [+] |
line wrap: on
line diff
--- a/README.md Mon Aug 13 16:21:26 2018 +0200 +++ b/README.md Mon Aug 13 16:21:38 2018 +0200 @@ -38,7 +38,7 @@ cp cmd/gemma/gemma.toml.example gemma.toml ``` -- Edit `gemma.toml`, some parameters you propably want to change: +- Edit `gemma.toml`, some parameters you probably want to change: * `host` and `port` to make gemma listen on a public interface * `service-password` to match the password for "gemma_service" user
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/gemma/geoserver.go Mon Aug 13 16:21:38 2018 +0200 @@ -0,0 +1,188 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "net/http" + + "gemma.intevation.de/gemma/config" + "gemma.intevation.de/gemma/misc" +) + +const ( + workspaceName = "gemma" + datastoreName = "gemma" + databaseScheme = "waterway" + databaseType = "postgis" +) + +const startupSQL = `SET SESSION AUTHORIZATION waterway_user;` + +func basicAuth(user, password string) func(req *http.Request) { + auth := "Basic " + misc.BasicAuth(user, password) + return func(req *http.Request) { + req.Header.Add("Authorization", auth) + } +} + +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 + const createJSON = `{"workspace":{"name":"` + workspaceName + `"}}` + + req, err = http.NewRequest( + http.MethodPost, + url+"/rest/workspaces", + bytes.NewReader([]byte(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 workspace. + 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 + } + + 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()}, + {"schema", databaseScheme}, + {"user", config.SysAdmin()}, + {"passwd", config.SysAdminPassword()}, + {"dbtype", databaseType}, + {"Session startup SQL", startupSQL}, + }, + }, + }, + } + 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 prepareGeoServer() error { + + var ( + url = config.GeoServerURL() + //tables = config.GeoServerTables() + ) + + //if url == "" || len(tables) == 0 { + if url == "" { + 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: Create layers. + + return nil +}
--- a/cmd/gemma/main.go Mon Aug 13 16:21:26 2018 +0200 +++ b/cmd/gemma/main.go Mon Aug 13 16:21:38 2018 +0200 @@ -37,6 +37,13 @@ prepareConnectionPool() + // Do GeoServer setup in background. + go func() { + if err := prepareGeoServer(); err != nil { + log.Printf("warn: preparing GeoServer: %v\n", err) + } + }() + m := mux.NewRouter() controllers.BindRoutes(m)
--- a/config/config.go Mon Aug 13 16:21:26 2018 +0200 +++ b/config/config.go Mon Aug 13 16:21:38 2018 +0200 @@ -25,6 +25,9 @@ func ServiceUser() string { return viper.GetString("service-user") } func ServicePassword() string { return viper.GetString("service-password") } +func SysAdmin() string { return viper.GetString("sys-admin") } +func SysAdminPassword() string { return viper.GetString("sys-admin-password") } + func MailHost() string { return viper.GetString("mail-host") } func MailPort() uint { return uint(viper.GetInt32("mail-port")) } func MailUser() string { return viper.GetString("mail-user") } @@ -36,63 +39,82 @@ func ExternalWFSs() map[string]interface{} { return viper.GetStringMap("external-wfs") } +func GeoServerURL() string { return viper.GetString("geoserver-url") } +func GeoServerUser() string { return viper.GetString("geoserver-user") } +func GeoServerPassword() string { return viper.GetString("geoserver-password") } +func GeoServerTables() []string { return viper.GetStringSlice("geoserver-tables") } + var RootCmd = &cobra.Command{ Use: "gemma", Short: "gemma is a server for waterway monitoring and management", } +var allowedOrigins = []string{ + // TODO: Fill me! +} + +var geoTables = []string{ + // TODO: Fill me! +} + func init() { cobra.OnInitialize(initConfig) fl := RootCmd.PersistentFlags() fl.StringVarP(&configFile, "config", "c", "", "config file (default is $HOME/.gemma.toml)") - fl.StringP("dbhost", "H", "localhost", "host of the database") - fl.UintP("dbport", "P", 5432, "port of the database") - fl.StringP("dbname", "d", "gemma", "name of the database") - fl.StringP("dbssl", "S", "prefer", "SSL mode of the database") - - fl.StringP("sessions", "s", "", "path to the sessions file") - - fl.StringP("web", "w", "", "path to the web files") - fl.StringP("host", "o", "localhost", "host of the web app") - fl.UintP("port", "p", 8000, "port of the web app") - - fl.String("service-user", "postgres", "user to do service tasks") - fl.String("service-password", "", "password of user to do service tasks") - - fl.String("mail-host", "localhost", "server to send mail with") - fl.Uint("mail-port", 465, "port of server to send mail with") - fl.String("mail-user", "gemma", "user to send mail with") - fl.String("mail-password", "", "password of user to send mail with") - fl.String("mail-from", "noreplay@localhost", "from line of mails") - fl.String("mail-helo", "localhost", "name of server to send mail from.") - - fl.StringSlice("allowed-origins", []string{"foo.org"}, "allow access for remote origins") - vbind := func(name string) { viper.BindPFlag(name, fl.Lookup(name)) } - vbind("dbhost") - vbind("dbport") - vbind("dbname") - vbind("dbssl") + str := func(name, value, usage string) { + fl.String(name, value, usage) + vbind(name) + } + strP := func(name, shorthand, value, usage string) { + fl.StringP(name, shorthand, value, usage) + vbind(name) + } + ui := func(name string, value uint, usage string) { + fl.Uint(name, value, usage) + vbind(name) + } + uiP := func(name, shorthand string, value uint, usage string) { + fl.UintP(name, shorthand, value, usage) + vbind(name) + } + strSl := func(name string, value []string, usage string) { + fl.StringSlice(name, value, usage) + vbind(name) + } - vbind("sessions") - - vbind("web") - vbind("host") - vbind("port") + strP("dbhost", "H", "localhost", "host of the database") + uiP("dbport", "P", 5432, "port of the database") + strP("dbname", "d", "gemma", "name of the database") + strP("dbssl", "S", "prefer", "SSL mode of the database") - vbind("service-user") - vbind("service-password") + strP("sessions", "s", "", "path to the sessions file") + + strP("web", "w", "", "path to the web files") + strP("host", "o", "localhost", "host of the web app") + uiP("port", "p", 8000, "port of the web app") + + str("service-user", "postgres", "user to do service tasks") + str("service-password", "", "password of user to do service tasks") + + str("sys-admin", "postgres", "user to do admin tasks") + str("sys-admin-password", "", "password of user to do admin tasks") - vbind("mail-host") - vbind("mail-port") - vbind("mail-user") - vbind("mail-password") - vbind("mail-from") - vbind("mail-helo") + str("mail-host", "localhost", "server to send mail with") + ui("mail-port", 465, "port of server to send mail with") + str("mail-user", "gemma", "user to send mail with") + str("mail-password", "", "password of user to send mail with") + str("mail-from", "noreplay@localhost", "from line of mails") + str("mail-helo", "localhost", "name of server to send mail from.") - vbind("allowed-origins") + strSl("allowed-origins", allowedOrigins, "allow access for remote origins") + + str("geoserver-url", "http://localhost:8080/geoserver", "URL to GeoServer") + str("geoserver-user", "admin", "GeoServer user") + str("geoserver-password", "geoserver", "GeoServer password") + strSl("geoserver-tables", geoTables, "tables to publish with GeoServer") } func initConfig() {
--- a/docs/schnittstellen.txt Mon Aug 13 16:21:26 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -| Route | Methode | Daten | *PUC | JSON | HTTP-Code | Anmerkung | -|--------------------------+----------+-------------------------------------------------------+--------------+----------------------------------------------------+---------------+------------------------------------------------+ -| /api/users | GET | Liste an Usern | | | 200, 500 | | -| /api/users | POST | user, password, role, email, country, *extent | APUC3, APUC4 | | 201, 400, 500 | | -| /api/users/{user} | GET | | APUC3, APUC4 | login, password, role, email, country, extent zoom | 201, 500 | | -| /api/users/{user} | DELETE | | APUC3 | | 204, 500 | | -| /api/users/{user} | PUT | user, password, role, email, country, *extent | APUC3 | login, password, role, email, country, extent zoom | 200, 500 | | -| /api/users/passwordreset | POST | user | GPUC3 | user | 200, 500 | | -| /api/login | GET/POST | user, password -> token im Result-JSON | APUC1 | | 200, 500 | | -| /api/logout | GET/POST | | APUC2 | | 200, 500 | | -| /api/renew | GET/POST | | APUC2 | | 200, 500 | | -| /api/health/hardware | GET | | APUC8 | | 200, 500 | Optionale Queryparameter: limit, from, from+to | -| /api/health/system | GET | | APUC8 | | 200, 500 | Optionale Queryparameter: limit, from, from+to | -| /api/health/access | GET | | APUC8 | | 200, 500 | Optionale Queryparameter: limit, from, from+to | -| /api/sendtestmail | POST | recipients: userids / an SAdmins, an WWAdmins | APUC9 | | 200, 500 | !Throttle! | -| /api/management | GET | aktuelle Konfiguration | APUC10 | | 200, 500 | | -| /api/management | PATCH | zu ändernde Parameter | APUC10 | | 200, 500 | | -| /api/templates | GET | Liste an Templates | APUC6 | | 200, 500 | | -| /api/templates | POST | Daten für ein neues Template | APUC6 | | 201, 500 | | -| /api/templates/{id} | GET | Metadaten für das Template | APUC6 | | 200, 500 | | -| /api/template/{id} | PATCH | zu ändernde Parameter | APUC6 | | 200, 500 | | -| /api/template/{id} | DELETE | | APUC6 | | 200, 500 | | -| /api/maps/print | POST | Metadaten für den Druck(?) | GPUC7 | | 200, 500 | | -| /api/search (?) | POST | Das zu suchende | GPUC10 | | 200, 500 | | -| /api/bottlenecks | GET | | SPUC1 | | 200, 500 | Limit? | -| /api/fairwaydimension | GET | Flusskilometerangabe, Zeitpunkt(?) | SPUC3 | | 200, 500 | SVG? |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/server-interface.txt Mon Aug 13 16:21:38 2018 +0200 @@ -0,0 +1,26 @@ +| Route | Methode | Daten | *PUC | JSON | HTTP-Code | Anmerkung | +|--------------------------+----------+-------------------------------------------------------+--------------+----------------------------------------------------+---------------+------------------------------------------------+ +| /api/users | GET | Liste an Usern | | | 200, 500 | | +| /api/users | POST | user, password, role, email, country, *extent | APUC3, APUC4 | | 201, 400, 500 | | +| /api/users/{user} | GET | | APUC3, APUC4 | login, password, role, email, country, extent zoom | 201, 500 | | +| /api/users/{user} | DELETE | | APUC3 | | 204, 500 | | +| /api/users/{user} | PUT | user, password, role, email, country, *extent | APUC3 | login, password, role, email, country, extent zoom | 200, 500 | | +| /api/users/passwordreset | POST | user | GPUC3 | user | 200, 500 | | +| /api/login | GET/POST | user, password -> token im Result-JSON | APUC1 | | 200, 500 | | +| /api/logout | GET/POST | | APUC2 | | 200, 500 | | +| /api/renew | GET/POST | | APUC2 | | 200, 500 | | +| /api/health/hardware | GET | | APUC8 | | 200, 500 | Optionale Queryparameter: limit, from, from+to | +| /api/health/system | GET | | APUC8 | | 200, 500 | Optionale Queryparameter: limit, from, from+to | +| /api/health/access | GET | | APUC8 | | 200, 500 | Optionale Queryparameter: limit, from, from+to | +| /api/sendtestmail | POST | recipients: userids / an SAdmins, an WWAdmins | APUC9 | | 200, 500 | !Throttle! | +| /api/management | GET | aktuelle Konfiguration | APUC10 | | 200, 500 | | +| /api/management | PATCH | zu ändernde Parameter | APUC10 | | 200, 500 | | +| /api/templates | GET | Liste an Templates | APUC6 | | 200, 500 | | +| /api/templates | POST | Daten für ein neues Template | APUC6 | | 201, 500 | | +| /api/templates/{id} | GET | Metadaten für das Template | APUC6 | | 200, 500 | | +| /api/template/{id} | PATCH | zu ändernde Parameter | APUC6 | | 200, 500 | | +| /api/template/{id} | DELETE | | APUC6 | | 200, 500 | | +| /api/maps/print | POST | Metadaten für den Druck(?) | GPUC7 | | 200, 500 | | +| /api/search (?) | POST | Das zu suchende | GPUC10 | | 200, 500 | | +| /api/bottlenecks | GET | | SPUC1 | | 200, 500 | Limit? | +| /api/fairwaydimension | GET | Flusskilometerangabe, Zeitpunkt(?) | SPUC3 | | 200, 500 | SVG? |
--- a/misc/encode.go Mon Aug 13 16:21:26 2018 +0200 +++ b/misc/encode.go Mon Aug 13 16:21:38 2018 +0200 @@ -1,6 +1,7 @@ package misc import ( + "encoding/base64" "encoding/binary" "io" ) @@ -68,3 +69,8 @@ w.Err = binary.Write(w.Writer, binary.BigEndian, []byte(s)) } } + +func BasicAuth(user, password string) string { + auth := user + ":" + password + return base64.StdEncoding.EncodeToString([]byte(auth)) +}