view pkg/config/config.go @ 4606:dfe9cde6a20c geoserver_sql_views

Reflect database model changes for SQL views in backend In principle, we could use many datasources with different database schemas, but this would imply changing GeoServer initialization, service filtering, endpoints and eventually more. Since we do not need it, just hard-code the schema name as a constant.
author Tom Gottfried <tom@intevation.de>
date Thu, 05 Sep 2019 12:23:31 +0200
parents 317d176ef38c
children 209b10f7bb2c
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018, 2019 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
//  * Bernhard E. Reiter <bernhard.reiter@intevation.de>

package config

import (
	"crypto/sha256"
	"fmt"
	"log"
	"sync"
	"time"

	homedir "github.com/mitchellh/go-homedir"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"gemma.intevation.de/gemma/pkg/common"
)

// This is not part of the persistent config.
var configFile string

// DBHost is the hostname of the database.
func DBHost() string { return viper.GetString("db-host") }

// DBPort is the port of the database.
func DBPort() uint { return uint(viper.GetInt32("db-port")) }

// DBName is the name of the database.
func DBName() string { return viper.GetString("db-name") }

// DBUser is the name of the user to connect to the database.
func DBUser() string { return viper.GetString("db-user") }

// DBPassword is the password to connect to the database.
func DBPassword() string { return viper.GetString("db-password") }

// DBSSLMode selects the SSL mode to encrypt the connection to the database.
func DBSSLMode() string { return viper.GetString("db-ssl") }

// SessionStore is the path to the session store.
// If empty the session store is kept in memory only.
func SessionStore() string { return viper.GetString("sessions") }

// Web is the root folder of the served web content.
func Web() string { return viper.GetString("web") }

// WebHost is the host to bind the web server to.
func WebHost() string { return viper.GetString("host") }

// WebPort is the port to bind the web server to.
func WebPort() uint { return uint(viper.GetInt32("port")) }

// MailHost is the server of the SMTP server.
func MailHost() string { return viper.GetString("mail-host") }

// MailPort is the port of the SMTP server.
func MailPort() uint { return uint(viper.GetInt32("mail-port")) }

// MailUser is the user to connect to the SMTP server.
func MailUser() string { return viper.GetString("mail-user") }

// MailPassword is the password of the user to connect to the SMTP server.
func MailPassword() string { return viper.GetString("mail-password") }

// MailFrom is the sender in the 'from' header in mails send.
func MailFrom() string { return viper.GetString("mail-from") }

// MailHelo is the helo message send to the SMTP server.
func MailHelo() string { return viper.GetString("mail-helo") }

// AllowedOrigins is a list of allowed host for CORS headers.
func AllowedOrigins() []string { return viper.GetStringSlice("allowed-origins") }

// GeoServerURL is the URL of the GeoServer used by gemma to serve map data.
func GeoServerURL() string { return viper.GetString("geoserver-url") }

// GeoServerUser is the adminstrative user to connect to the GeoServer.
func GeoServerUser() string { return viper.GetString("geoserver-user") }

// GeoServerPassword is the password of the adminstrative user
// to connect to the GeoServer.
func GeoServerPassword() string { return viper.GetString("geoserver-password") }

// GeoServerClean is a flag to indicate that the GeoServer setup for
// gemma should be freshly created at boot time.
// This is useful in case of gemma update.
// If false the only missing parts are added to the GeoServer setup.
// This should be the default mode when running gemma after an update
// as it reduces the pressure on the GeoServer and enables faster
// rebooting.
func GeoServerClean() bool { return viper.GetBool("geoserver-clean") }

// TmpDir is the path where to store temporary files.
// If left empty the system default for temporary files is used.
func TmpDir() string { return viper.GetString("tmp-dir") }

// PublishedConfig is a name of a JSON file where extra configuration is stored
// to be served to to the web client.
func PublishedConfig() string { return viper.GetString("published-config") }

// SOAPTimeout is the timeout till a SOAP request is canceled.
func SOAPTimeout() time.Duration { return viper.GetDuration("soap-timeout") }

var (
	proxyKeyOnce       sync.Once
	proxyKey           []byte
	proxyPrefixOnce    sync.Once
	proxyPrefix        string
	externalURLOnce    sync.Once
	externalURL        string
	sessionTimeoutOnce sync.Once
	sessionTimeout     time.Duration
)

// ProxyKey is a crytographic key to sign the URLs generated by the proxy.
// Use this to ensure that only known URLs are reachable over the proxy.
// If left blank a random key is generated a gemma boot time.
// Setting this value in the configuration will allow browsing proxy
// generated URL across gemma reboot.
// Use a strong secret key here (like pwgen -s 20).
func ProxyKey() []byte {
	fetchKey := func() {
		if proxyKey == nil {
			key := []byte(viper.GetString("proxy-key"))
			if len(key) == 0 {
				key = common.GenerateRandomKey(64)
			}
			hash := sha256.New()
			hash.Write(key)
			proxyKey = hash.Sum(nil)
		}
	}
	proxyKeyOnce.Do(fetchKey)
	return proxyKey
}

// ProxyPrefix is the prefix used in generated URLs by the proxy.
// You may need to set this if you run gemma behind a proxy
// on a specific domain.
func ProxyPrefix() string {
	fetchPrefix := func() {
		if proxyPrefix == "" {
			if proxyPrefix = viper.GetString("proxy-prefix"); proxyPrefix == "" {
				proxyPrefix = fmt.Sprintf("http://%s:%d", WebHost(), WebPort())
			}
		}
	}
	proxyPrefixOnce.Do(fetchPrefix)
	return proxyPrefix
}

// ExternalURL is the URL to find this server from the outside.
func ExternalURL() string {
	fetchExternal := func() {
		if externalURL == "" {
			if externalURL = viper.GetString("external-url"); externalURL == "" {
				externalURL = fmt.Sprintf("http://%s:%d", WebHost(), WebPort())
			}
		}
	}
	externalURLOnce.Do(fetchExternal)
	return externalURL
}

// SessionTimeout is the duration until a session expires if not renewed
func SessionTimeout() time.Duration {
	fetchTimeout := func() {
		if sessionTimeout == 0 {
			sessionTimeout = viper.GetDuration("session-timeout")
			if sessionTimeout <= 0 {
				log.Println("warn: non-positive session-timeout configured.")
			}
		}
	}
	sessionTimeoutOnce.Do(fetchTimeout)
	return sessionTimeout
}

// SchemaDirs are the root directories where to find schema files.
func SchemaDirs() string { return viper.GetString("schema-dirs") }

// RootCmd is cobra command to be bound th the cobra/viper infrastructure.
var RootCmd = &cobra.Command{
	Use:   "gemma",
	Short: "gemma is a server for waterway monitoring and management",
}

var allowedOrigins = []string{
	// TODO: Fill me!
}

func init() {
	cobra.OnInitialize(initConfig)
	fl := RootCmd.PersistentFlags()
	fl.StringVarP(&configFile, "config", "c", "", "config file (default is $HOME/.gemma.toml)")

	vbind := func(name string) { viper.BindPFlag(name, fl.Lookup(name)) }

	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)
	}
	bl := func(name string, value bool, usage string) {
		fl.Bool(name, value, usage)
		vbind(name)
	}
	d := func(name string, value time.Duration, usage string) {
		fl.Duration(name, value, usage)
		vbind(name)
	}

	strP("db-host", "H", "localhost", "host of the database")
	uiP("db-port", "P", 5432, "port of the database")
	strP("db-name", "d", "gemma", "name of the database")
	str("db-user", "meta_login", "Metamorphic database user")
	str("db-password", "", "Metamorphic database user password")
	strP("db-ssl", "S", "prefer", "SSL mode of the database")

	strP("sessions", "s", "", "path to the sessions file")
	d("session-timeout", 3*time.Hour, "duration until sessions expire")

	strP("web", "w", "./web", "path to the web files")
	strP("host", "o", "localhost", "host of the web app")
	uiP("port", "p", 8000, "port of the web app")

	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 authenticate against mail-host.\n"+
		"Leave empty for trying to send without auth.")
	str("mail-password", "", "password of user to send mail with")
	str("mail-from", "noreply@localhost", "from line of mails")
	str("mail-helo", "localhost", "name of server to send mail from.")

	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")
	bl("geoserver-clean", false, "Clean GeoServer setup")

	str("proxy-key", "", "signing key for proxy URLs.\n"+
		"Defaults to random key.")
	str("proxy-prefix", "", "URL prefix of proxy.\n"+
		"Defaults to 'http://${host}:${port}'")

	str("external-url", "", "URL to find the server from the outside.\n"+
		"Defaults to 'http://${host}:${port}'")

	str("tmp-dir", "", "Temp directory of gemma server.\n"+
		"Defaults to system temp directory.")

	str("schema-dirs", ".", "Directories to find XSD schema files in (recursive).")

	str("published-config", "", "path to a config file served to client.")

	d("soap-timeout", 3*time.Minute, "Timeout till a SOAP request is canceled.")
}

var (
	configCond  = sync.NewCond(new(sync.Mutex))
	configReady bool
)

// Ready tells if the configuration is ready to use..
func Ready() {
	configCond.L.Lock()
	defer configCond.L.Unlock()
	configReady = true
	configCond.Broadcast()
}

// WaitReady blocks until the configuration is ready to use.
// Call this if you have a go routine that needs configuration
// support. This guarantees that the initialization is done
// before accessing the configuration data.
func WaitReady() {
	configCond.L.Lock()
	defer configCond.L.Unlock()
	for !configReady {
		configCond.Wait()
	}
}

func initConfig() {
	// Don't forget to read config either from cfgFile or from home directory!
	if configFile != "" {
		// Use config file from the flag.
		viper.SetConfigFile(configFile)
	} else {
		// Find home directory.
		home, err := homedir.Dir()
		if err != nil {
			log.Fatalf("error: %v\n", err)
		}

		// Search config in home directory with name ".cobra" (without extension).
		viper.AddConfigPath(home)
		viper.SetConfigName(".gemma")
	}
	if err := viper.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok && configFile == "" {
			// Don't bother if not found.
			return
		}
		log.Fatalf("Can't read config: %v\n", err)
	}
}