The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/storage/unified/sql/db/dbimpl/db_engine.go

192 lines
5.7 KiB

package dbimpl
import (
"cmp"
"fmt"
"strings"
"time"
"github.com/go-sql-driver/mysql"
"github.com/grafana/dskit/crypto/tls"
"github.com/grafana/grafana/pkg/util/xorm"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
)
// tlsConfigName is the name of the TLS config that we register with the MySQL
// driver.
const tlsConfigName = "db_engine_tls"
func getEngine(cfg *setting.Cfg) (*xorm.Engine, error) {
dbSection := cfg.SectionWithEnvOverrides("database")
dbType := dbSection.Key("type").String()
if dbType == "" {
return nil, fmt.Errorf("no database type specified")
}
switch dbType {
case dbTypeMySQL, dbTypePostgres, dbTypeSQLite:
config, err := sqlstore.NewDatabaseConfig(cfg, nil)
if err != nil {
return nil, nil
}
engine, err := xorm.NewEngine(dbType, config.ConnectionString)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}
return engine, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", dbType)
}
}
// Deprecated: use getEngine instead
func getEngineMySQL(getter confGetter) (*xorm.Engine, error) {
config := mysql.NewConfig()
config.User = getter.String("user")
// accept the core Grafana jargon of `password` as well, originally Unified
// Storage used `pass`
config.Passwd = cmp.Or(getter.String("pass"), getter.String("password"))
config.Net = "tcp"
config.Addr = getter.String("host")
config.DBName = getter.String("name")
config.Params = map[string]string{
// See: https://dev.mysql.com/doc/refman/en/sql-mode.html
"@@SESSION.sql_mode": "ANSI",
}
config.Collation = "utf8mb4_unicode_ci"
config.Loc = time.UTC
config.AllowNativePasswords = true
config.ClientFoundRows = true
config.ParseTime = true
// Setup TLS for the database connection if configured.
if err := configureTLS(getter, config); err != nil {
return nil, fmt.Errorf("failed to configure TLS: %w", err)
}
// allow executing multiple SQL statements in a single roundtrip, and also
// enable executing the CALL statement to run stored procedures that execute
// multiple SQL statements.
//config.MultiStatements = true
if err := getter.Err(); err != nil {
return nil, fmt.Errorf("config error: %w", err)
}
if strings.HasPrefix(config.Addr, "/") {
config.Net = "unix"
}
engine, err := xorm.NewEngine(db.DriverMySQL, config.FormatDSN())
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}
engine.SetMaxOpenConns(0)
engine.SetMaxIdleConns(2)
engine.SetConnMaxLifetime(4 * time.Hour)
return engine, nil
}
func configureTLS(getter confGetter, config *mysql.Config) error {
sslMode := getter.String("ssl_mode")
if sslMode == "true" || sslMode == "skip-verify" {
tlsCfg := tls.ClientConfig{
CAPath: getter.String("ca_cert_path"),
CertPath: getter.String("client_cert_path"),
KeyPath: getter.String("client_key_path"),
ServerName: getter.String("server_cert_name"),
}
rawTLSCfg, err := tlsCfg.GetTLSConfig()
if err != nil {
return fmt.Errorf("failed to get TLS config for mysql: %w", err)
}
if sslMode == "skip-verify" {
rawTLSCfg.InsecureSkipVerify = true
}
if err := mysql.RegisterTLSConfig(tlsConfigName, rawTLSCfg); err != nil {
return fmt.Errorf("failed to register TLS config for mysql: %w", err)
}
config.TLSConfig = tlsConfigName
}
// If the TLS mode is set in the database config, we need to set it here.
if tls := getter.String("tls"); tls != "" {
// If the user has provided TLS certs, we don't want to use the tls=<value>, as
// they would override the TLS config that we set above. They both use the same
// parameter, so we need to check for that.
if sslMode == "true" {
return fmt.Errorf("cannot provide tls certs and tls=<value> at the same time")
}
config.Params["tls"] = tls
}
return nil
}
// Deprecated: use getEngine instead
func getEnginePostgres(getter confGetter) (*xorm.Engine, error) {
dsnKV := map[string]string{
"user": getter.String("user"),
// accept the core Grafana jargon of `password` as well, originally
// Unified Storage used `pass`
"password": cmp.Or(getter.String("pass"), getter.String("password")),
"dbname": getter.String("name"),
"sslmode": cmp.Or(getter.String("ssl_mode"), "disable"),
"sslsni": getter.String("ssl_sni"),
"sslrootcert": getter.String("ca_cert_path"),
"sslkey": getter.String("client_key_path"),
"sslcert": getter.String("client_cert_path"),
}
// TODO: probably interesting:
// "passfile", "statement_timeout", "lock_timeout", "connect_timeout"
// TODO: for CockroachDB, we probably need to use the following:
// dsnKV["options"] = "-c enable_experimental_alter_column_type_general=true"
// Or otherwise specify it as:
// dsnKV["enable_experimental_alter_column_type_general"] = "true"
// TODO: do we want to support these options in the DSN as well?
// "sslpassword", "krbspn", "krbsrvname", "target_session_attrs", "service", "servicefile"
// More on Postgres connection string parameters:
// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
hostport := getter.String("host")
if err := getter.Err(); err != nil {
return nil, fmt.Errorf("config error: %w", err)
}
host, port, err := splitHostPortDefault(hostport, "127.0.0.1", "5432")
if err != nil {
return nil, fmt.Errorf("invalid host: %w", err)
}
dsnKV["host"] = host
dsnKV["port"] = port
dsn, err := MakeDSN(dsnKV)
if err != nil {
return nil, fmt.Errorf("error building DSN: %w", err)
}
// FIXME: get rid of xorm
engine, err := xorm.NewEngine(db.DriverPostgres, dsn)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}
return engine, nil
}