2016-09-15 01:11:57 +00:00
|
|
|
package sql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
|
2016-11-22 23:35:46 +00:00
|
|
|
"github.com/Sirupsen/logrus"
|
2016-09-15 01:11:57 +00:00
|
|
|
"github.com/coreos/dex/storage"
|
2017-02-21 23:00:22 +00:00
|
|
|
"github.com/lib/pq"
|
|
|
|
sqlite3 "github.com/mattn/go-sqlite3"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// postgres error codes
|
|
|
|
pgErrUniqueViolation = "23505" // unique_violation
|
2016-09-15 01:11:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// SQLite3 options for creating an SQL db.
|
|
|
|
type SQLite3 struct {
|
|
|
|
// File to
|
2016-11-03 21:32:23 +00:00
|
|
|
File string `json:"file"`
|
2016-09-15 01:11:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Open creates a new storage implementation backed by SQLite3
|
2016-11-22 23:35:46 +00:00
|
|
|
func (s *SQLite3) Open(logger logrus.FieldLogger) (storage.Storage, error) {
|
|
|
|
conn, err := s.open(logger)
|
2016-10-04 19:57:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-10-13 01:48:09 +00:00
|
|
|
return conn, nil
|
2016-09-15 01:11:57 +00:00
|
|
|
}
|
|
|
|
|
2016-11-22 23:35:46 +00:00
|
|
|
func (s *SQLite3) open(logger logrus.FieldLogger) (*conn, error) {
|
2016-09-15 01:11:57 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-02-21 23:00:22 +00:00
|
|
|
|
|
|
|
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}
|
2016-09-15 01:11:57 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
SSL PostgresSSL `json:"ssl" yaml:"ssl"`
|
|
|
|
|
|
|
|
ConnectionTimeout int // Seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open creates a new storage implementation backed by Postgres.
|
2016-11-22 23:35:46 +00:00
|
|
|
func (p *Postgres) Open(logger logrus.FieldLogger) (storage.Storage, error) {
|
|
|
|
conn, err := p.open(logger)
|
2016-10-04 19:57:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-10-13 01:48:09 +00:00
|
|
|
return conn, nil
|
2016-09-15 01:11:57 +00:00
|
|
|
}
|
|
|
|
|
2016-11-22 23:35:46 +00:00
|
|
|
func (p *Postgres) open(logger logrus.FieldLogger) (*conn, error) {
|
2016-09-15 01:11:57 +00:00
|
|
|
v := url.Values{}
|
|
|
|
set := func(key, val string) {
|
|
|
|
if val != "" {
|
|
|
|
v.Set(key, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
set("connect_timeout", strconv.Itoa(p.ConnectionTimeout))
|
|
|
|
set("sslkey", p.SSL.KeyFile)
|
|
|
|
set("sslcert", p.SSL.CertFile)
|
|
|
|
set("sslrootcert", p.SSL.CAFile)
|
|
|
|
if p.SSL.Mode == "" {
|
|
|
|
// Assume the strictest mode if unspecified.
|
|
|
|
p.SSL.Mode = sslVerifyFull
|
|
|
|
}
|
|
|
|
set("sslmode", p.SSL.Mode)
|
|
|
|
|
|
|
|
u := url.URL{
|
|
|
|
Scheme: "postgres",
|
|
|
|
Host: p.Host,
|
|
|
|
Path: "/" + p.Database,
|
|
|
|
RawQuery: v.Encode(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.User != "" {
|
|
|
|
if p.Password != "" {
|
|
|
|
u.User = url.UserPassword(p.User, p.Password)
|
|
|
|
} else {
|
|
|
|
u.User = url.User(p.User)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
db, err := sql.Open("postgres", u.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-21 23:00:22 +00:00
|
|
|
|
|
|
|
errCheck := func(err error) bool {
|
|
|
|
sqlErr, ok := err.(*pq.Error)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return sqlErr.Code == pgErrUniqueViolation
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &conn{db, flavorPostgres, logger, errCheck}
|
2016-09-15 01:11:57 +00:00
|
|
|
if _, err := c.migrate(); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to perform migrations: %v", err)
|
|
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|