This repository has been archived on 2022-08-17. You can view files and clone it, but cannot push or open issues or pull requests.
dex/storage/sql/config.go
Stephan Renatus 73fdf4f75b
storage/sql/postgres: expose stdlib tunables, set them for tests
- adapted TestUnmarshalConfig to ensure the fields are read in
- added a test to see that at least MaxOpenConns works:
  - this is only exposed through (*db).Stats() in go 1.11, so this test
    has a build tag
  - the other two configurables can't be read back, so we've got to
    trust that the mechanism works given the one instance that's tested..

Signed-off-by: Stephan Renatus <srenatus@chef.io>
2018-11-30 09:55:01 +01:00

218 lines
4.7 KiB
Go

package sql
import (
"database/sql"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"time"
"github.com/lib/pq"
sqlite3 "github.com/mattn/go-sqlite3"
"github.com/sirupsen/logrus"
"github.com/dexidp/dex/storage"
)
const (
// postgres error codes
pgErrUniqueViolation = "23505" // unique_violation
)
// SQLite3 options for creating an SQL db.
type SQLite3 struct {
// File to
File string `json:"file"`
}
// Open creates a new storage implementation backed by SQLite3
func (s *SQLite3) Open(logger logrus.FieldLogger) (storage.Storage, error) {
conn, err := s.open(logger)
if err != nil {
return nil, err
}
return conn, nil
}
func (s *SQLite3) open(logger logrus.FieldLogger) (*conn, error) {
db, err := sql.Open("sqlite3", s.File)
if err != nil {
return nil, err
}
if s.File == ":memory:" {
// sqlite3 uses file locks to coordinate concurrent access. In memory
// doesn't support this, so limit the number of connections to 1.
db.SetMaxOpenConns(1)
}
errCheck := func(err error) bool {
sqlErr, ok := err.(sqlite3.Error)
if !ok {
return false
}
return sqlErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey
}
c := &conn{db, flavorSQLite3, logger, errCheck}
if _, err := c.migrate(); err != nil {
return nil, fmt.Errorf("failed to perform migrations: %v", err)
}
return c, nil
}
const (
sslDisable = "disable"
sslRequire = "require"
sslVerifyCA = "verify-ca"
sslVerifyFull = "verify-full"
)
// PostgresSSL represents SSL options for Postgres databases.
type PostgresSSL struct {
Mode string
CAFile string
// Files for client auth.
KeyFile string
CertFile string
}
// Postgres options for creating an SQL db.
type Postgres struct {
Database string
User string
Password string
Host string
Port uint16
SSL PostgresSSL `json:"ssl" yaml:"ssl"`
ConnectionTimeout int // Seconds
// database/sql tunables, see
// https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime and below
// Note: defaults will be set if these are 0
MaxOpenConns int // default: 5
MaxIdleConns int // default: 5
ConnMaxLifetime int // Seconds, default: not set
}
// Open creates a new storage implementation backed by Postgres.
func (p *Postgres) Open(logger logrus.FieldLogger) (storage.Storage, error) {
conn, err := p.open(logger, p.createDataSourceName())
if err != nil {
return nil, err
}
return conn, nil
}
var strEsc = regexp.MustCompile(`([\\'])`)
func dataSourceStr(str string) string {
return "'" + strEsc.ReplaceAllString(str, `\$1`) + "'"
}
// createDataSourceName takes the configuration provided via the Postgres
// struct to create a data-source name that Go's database/sql package can
// make use of.
func (p *Postgres) createDataSourceName() string {
parameters := []string{}
addParam := func(key, val string) {
parameters = append(parameters, fmt.Sprintf("%s=%s", key, val))
}
addParam("connect_timeout", strconv.Itoa(p.ConnectionTimeout))
// detect host:port for backwards-compatibility
host, port, err := net.SplitHostPort(p.Host)
if err != nil {
// not host:port, probably unix socket or bare address
host = p.Host
if p.Port != 0 {
port = strconv.Itoa(int(p.Port))
}
}
if host != "" {
addParam("host", dataSourceStr(host))
}
if port != "" {
addParam("port", port)
}
if p.User != "" {
addParam("user", dataSourceStr(p.User))
}
if p.Password != "" {
addParam("password", dataSourceStr(p.Password))
}
if p.Database != "" {
addParam("dbname", dataSourceStr(p.Database))
}
if p.SSL.Mode == "" {
// Assume the strictest mode if unspecified.
addParam("sslmode", dataSourceStr(sslVerifyFull))
} else {
addParam("sslmode", dataSourceStr(p.SSL.Mode))
}
if p.SSL.CAFile != "" {
addParam("sslrootcert", dataSourceStr(p.SSL.CAFile))
}
if p.SSL.CertFile != "" {
addParam("sslcert", dataSourceStr(p.SSL.CertFile))
}
if p.SSL.KeyFile != "" {
addParam("sslkey", dataSourceStr(p.SSL.KeyFile))
}
return strings.Join(parameters, " ")
}
func (p *Postgres) open(logger logrus.FieldLogger, dataSourceName string) (*conn, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
// set database/sql tunables if configured
if p.ConnMaxLifetime != 0 {
db.SetConnMaxLifetime(time.Duration(p.ConnMaxLifetime) * time.Second)
}
if p.MaxIdleConns == 0 {
db.SetMaxIdleConns(5)
} else {
db.SetMaxIdleConns(p.MaxIdleConns)
}
if p.MaxOpenConns == 0 {
db.SetMaxOpenConns(5)
} else {
db.SetMaxOpenConns(p.MaxOpenConns)
}
errCheck := func(err error) bool {
sqlErr, ok := err.(*pq.Error)
if !ok {
return false
}
return sqlErr.Code == pgErrUniqueViolation
}
c := &conn{db, flavorPostgres, logger, errCheck}
if _, err := c.migrate(); err != nil {
return nil, fmt.Errorf("failed to perform migrations: %v", err)
}
return c, nil
}