summaryrefslogtreecommitdiffstats
path: root/pkg/config/database.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/config/database.go')
-rw-r--r--pkg/config/database.go175
1 files changed, 175 insertions, 0 deletions
diff --git a/pkg/config/database.go b/pkg/config/database.go
new file mode 100644
index 0000000..b42ff8e
--- /dev/null
+++ b/pkg/config/database.go
@@ -0,0 +1,175 @@
+package config
+
+import (
+ "fmt"
+ "github.com/go-sql-driver/mysql"
+ "github.com/icinga/icingadb/pkg/driver"
+ "github.com/icinga/icingadb/pkg/icingadb"
+ "github.com/icinga/icingadb/pkg/logging"
+ "github.com/icinga/icingadb/pkg/utils"
+ "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx/reflectx"
+ "github.com/pkg/errors"
+ "net"
+ "net/url"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+var registerDriverOnce sync.Once
+
+// Database defines database client configuration.
+type Database struct {
+ Type string `yaml:"type" default:"mysql"`
+ Host string `yaml:"host"`
+ Port int `yaml:"port"`
+ Database string `yaml:"database"`
+ User string `yaml:"user"`
+ Password string `yaml:"password"`
+ TlsOptions TLS `yaml:",inline"`
+ Options icingadb.Options `yaml:"options"`
+}
+
+// Open prepares the DSN string and driver configuration,
+// calls sqlx.Open, but returns *icingadb.DB.
+func (d *Database) Open(logger *logging.Logger) (*icingadb.DB, error) {
+ registerDriverOnce.Do(func() {
+ driver.Register(logger)
+ })
+
+ var dsn string
+ switch d.Type {
+ case "mysql":
+ config := mysql.NewConfig()
+
+ config.User = d.User
+ config.Passwd = d.Password
+
+ if d.isUnixAddr() {
+ config.Net = "unix"
+ config.Addr = d.Host
+ } else {
+ config.Net = "tcp"
+ port := d.Port
+ if port == 0 {
+ port = 3306
+ }
+ config.Addr = net.JoinHostPort(d.Host, fmt.Sprint(port))
+ }
+
+ config.DBName = d.Database
+ config.Timeout = time.Minute
+ config.Params = map[string]string{"sql_mode": "ANSI_QUOTES"}
+
+ tlsConfig, err := d.TlsOptions.MakeConfig(d.Host)
+ if err != nil {
+ return nil, err
+ }
+
+ if tlsConfig != nil {
+ config.TLSConfig = "icingadb"
+ if err := mysql.RegisterTLSConfig(config.TLSConfig, tlsConfig); err != nil {
+ return nil, errors.Wrap(err, "can't register TLS config")
+ }
+ }
+
+ dsn = config.FormatDSN()
+ case "pgsql":
+ uri := &url.URL{
+ Scheme: "postgres",
+ User: url.UserPassword(d.User, d.Password),
+ Path: "/" + url.PathEscape(d.Database),
+ }
+
+ query := url.Values{
+ "connect_timeout": {"60"},
+ "binary_parameters": {"yes"},
+
+ // Host and port can alternatively be specified in the query string. lib/pq can't parse the connection URI
+ // if a Unix domain socket path is specified in the host part of the URI, therefore always use the query
+ // string. See also https://github.com/lib/pq/issues/796
+ "host": {d.Host},
+ }
+ if d.Port != 0 {
+ query["port"] = []string{strconv.FormatInt(int64(d.Port), 10)}
+ }
+
+ if _, err := d.TlsOptions.MakeConfig(d.Host); err != nil {
+ return nil, err
+ }
+
+ if d.TlsOptions.Enable {
+ if d.TlsOptions.Insecure {
+ query["sslmode"] = []string{"require"}
+ } else {
+ query["sslmode"] = []string{"verify-full"}
+ }
+
+ if d.TlsOptions.Cert != "" {
+ query["sslcert"] = []string{d.TlsOptions.Cert}
+ }
+
+ if d.TlsOptions.Key != "" {
+ query["sslkey"] = []string{d.TlsOptions.Key}
+ }
+
+ if d.TlsOptions.Ca != "" {
+ query["sslrootcert"] = []string{d.TlsOptions.Ca}
+ }
+ } else {
+ query["sslmode"] = []string{"disable"}
+ }
+
+ uri.RawQuery = query.Encode()
+ dsn = uri.String()
+ default:
+ return nil, unknownDbType(d.Type)
+ }
+
+ db, err := sqlx.Open("icingadb-"+d.Type, dsn)
+ if err != nil {
+ return nil, errors.Wrap(err, "can't open database")
+ }
+
+ db.SetMaxIdleConns(d.Options.MaxConnections / 3)
+ db.SetMaxOpenConns(d.Options.MaxConnections)
+
+ db.Mapper = reflectx.NewMapperFunc("db", func(s string) string {
+ return utils.Key(s, '_')
+ })
+
+ return icingadb.NewDb(db, logger, &d.Options), nil
+}
+
+// Validate checks constraints in the supplied database configuration and returns an error if they are violated.
+func (d *Database) Validate() error {
+ switch d.Type {
+ case "mysql", "pgsql":
+ default:
+ return unknownDbType(d.Type)
+ }
+
+ if d.Host == "" {
+ return errors.New("database host missing")
+ }
+
+ if d.User == "" {
+ return errors.New("database user missing")
+ }
+
+ if d.Database == "" {
+ return errors.New("database name missing")
+ }
+
+ return d.Options.Validate()
+}
+
+func (d *Database) isUnixAddr() bool {
+ return strings.HasPrefix(d.Host, "/")
+}
+
+func unknownDbType(t string) error {
+ return errors.Errorf(`unknown database type %q, must be one of: "mysql", "pgsql"`, t)
+}