Update github.com/titpetric/factory
This commit is contained in:
@@ -51,7 +51,7 @@ require (
|
||||
github.com/steinfletcher/apitest v1.3.8
|
||||
github.com/steinfletcher/apitest-jsonpath v1.3.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/titpetric/factory v0.0.0-20190806200833-ae4b02b9e034
|
||||
github.com/titpetric/factory v0.0.0-20190828134837-8466c9bef13f
|
||||
go.uber.org/atomic v1.5.0
|
||||
go.uber.org/zap v1.13.0
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
|
||||
|
||||
@@ -232,6 +232,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/titpetric/factory v0.0.0-20190806200833-ae4b02b9e034 h1:H2WfXOKTtF37QV4McGIRH37zNa00NgZjRJ/5wwxqSv8=
|
||||
github.com/titpetric/factory v0.0.0-20190806200833-ae4b02b9e034/go.mod h1:VFd2XRrQZoX9cOxpeZezKpOlXDwU/dbRejKLjwP+xY8=
|
||||
github.com/titpetric/factory v0.0.0-20190828134837-8466c9bef13f h1:sTgdEUlmmNSU3QKe8n20jR26zYEKw6ciSuj919XobV4=
|
||||
github.com/titpetric/factory v0.0.0-20190828134837-8466c9bef13f/go.mod h1:VFd2XRrQZoX9cOxpeZezKpOlXDwU/dbRejKLjwP+xY8=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
|
||||
+2
-3
@@ -6,7 +6,7 @@ name: crust
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: crusttech/crust-builder:latest
|
||||
image: golang:1.12-alpine
|
||||
pull: always
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
@@ -16,8 +16,7 @@ steps:
|
||||
CI: travis
|
||||
commands:
|
||||
- go fmt ./...
|
||||
- wait-for-it.sh -t 60 --strict factory-db:3306 -- echo "database service is up"
|
||||
- make test
|
||||
- go test ./... -v -cover --tags="integration"
|
||||
|
||||
services:
|
||||
- name: factory-db
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
.PHONY: all test
|
||||
.PHONY: all
|
||||
|
||||
all:
|
||||
drone exec
|
||||
|
||||
test:
|
||||
gotest ./... -v -cover
|
||||
|
||||
+59
-42
@@ -20,12 +20,14 @@ import (
|
||||
|
||||
// DatabaseCredential is a configuration struct for a database connection
|
||||
type DatabaseCredential struct {
|
||||
DSN string
|
||||
DSN string
|
||||
DriverName string
|
||||
Connector func() (*sql.DB, error)
|
||||
}
|
||||
|
||||
// DatabaseFactory contains all database credentials and instances
|
||||
type DatabaseFactory struct {
|
||||
credentials map[string]*DatabaseCredential
|
||||
credentials map[string]DatabaseCredential
|
||||
instances map[string]*DB
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ var Database *DatabaseFactory
|
||||
|
||||
func init() {
|
||||
Database = &DatabaseFactory{}
|
||||
Database.credentials = make(map[string]*DatabaseCredential)
|
||||
Database.credentials = make(map[string]DatabaseCredential)
|
||||
Database.instances = make(map[string]*DB)
|
||||
}
|
||||
|
||||
@@ -48,10 +50,10 @@ func init() {
|
||||
// Example:
|
||||
//
|
||||
// ```
|
||||
// factory.Database.Add("default", "sqlapi:sqlapi@tcp(db1:3306)/sqlapi?collation=utf8mb4_general_ci")
|
||||
// factory.Database.Add("default", factory.DatabaseCredential{"mysql", "sqlapi:sqlapi@tcp(db1:3306)/sqlapi?collation=utf8mb4_general_ci"})
|
||||
// ```
|
||||
//
|
||||
// By default, additional options will be added to the credentials:
|
||||
// By default, additional options will be added to the credentials DSN:
|
||||
//
|
||||
// - collation will be set to `utf8_general_ci`
|
||||
// - parseTime will be set to `true`
|
||||
@@ -59,21 +61,12 @@ func init() {
|
||||
//
|
||||
// If your passed DSN will include any of these options, the default values will not
|
||||
// be applied, and your custom settings will be honored.
|
||||
func (r *DatabaseFactory) Add(name string, config interface{}) {
|
||||
switch val := config.(type) {
|
||||
case *string:
|
||||
r.credentials[name] = &DatabaseCredential{DSN: *val}
|
||||
case string:
|
||||
r.credentials[name] = &DatabaseCredential{DSN: val}
|
||||
case DatabaseCredential:
|
||||
r.credentials[name] = &val
|
||||
default:
|
||||
panic("factory.Database.Add can take config as string|*string|factory.DatabaseCredential")
|
||||
}
|
||||
func (r *DatabaseFactory) Add(name string, credentials DatabaseCredential) {
|
||||
credentials.DSN = r.CleanDSN(credentials.DriverName, credentials.DSN)
|
||||
r.credentials[name] = credentials
|
||||
}
|
||||
|
||||
// GetDSN returns the augmented DSN from the named database
|
||||
func (r *DatabaseFactory) GetDSN(name string) (string, error) {
|
||||
func (r *DatabaseFactory) CleanDSN(driverName string, dsn string) string {
|
||||
addOption := func(s, match, option string) string {
|
||||
if !strings.Contains(s, match) {
|
||||
s += option
|
||||
@@ -81,15 +74,20 @@ func (r *DatabaseFactory) GetDSN(name string) (string, error) {
|
||||
return s
|
||||
}
|
||||
|
||||
dsn = addOption(dsn, "?", "?")
|
||||
dsn = addOption(dsn, "collation=", "&collation=utf8_general_ci")
|
||||
dsn = addOption(dsn, "parseTime=", "&parseTime=true")
|
||||
dsn = addOption(dsn, "loc=", "&loc=Local")
|
||||
dsn = strings.Replace(dsn, "?&", "?", 1)
|
||||
return dsn
|
||||
}
|
||||
|
||||
// getCredentials returns the DatabaseCredential{} for a given db name
|
||||
func (r *DatabaseFactory) getCredentials(name string) (*DatabaseCredential, error) {
|
||||
if value, ok := r.credentials[name]; ok {
|
||||
value.DSN = addOption(value.DSN, "?", "?")
|
||||
value.DSN = addOption(value.DSN, "collation=", "&collation=utf8_general_ci")
|
||||
value.DSN = addOption(value.DSN, "parseTime=", "&parseTime=true")
|
||||
value.DSN = addOption(value.DSN, "loc=", "&loc=Local")
|
||||
value.DSN = strings.Replace(value.DSN, "?&", "?", 1)
|
||||
return value.DSN, nil
|
||||
return &value, nil
|
||||
}
|
||||
return "", errors.New("No configuration found for database: " + name)
|
||||
return nil, errors.New("No configuration found for database: " + name)
|
||||
}
|
||||
|
||||
// Get returns a database connection
|
||||
@@ -119,24 +117,43 @@ func (r *DatabaseFactory) Get(dbName ...string) (*DB, error) {
|
||||
if value, ok := r.instances[name]; ok {
|
||||
return value, nil
|
||||
}
|
||||
dsn, _ := r.GetDSN(name)
|
||||
if dsn != "" {
|
||||
handle, err := sqlx.Connect("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.instances[name] = &DB{
|
||||
handle,
|
||||
context.Background(),
|
||||
0,
|
||||
nil,
|
||||
&sql.TxOptions{
|
||||
ReadOnly: false,
|
||||
},
|
||||
logger.Silent{},
|
||||
}
|
||||
return r.instances[name], nil
|
||||
|
||||
credentials, err := r.getCredentials(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if credentials.DriverName == "" {
|
||||
return nil, errors.New("Credentials missing DriverName")
|
||||
}
|
||||
|
||||
var handle *sqlx.DB
|
||||
|
||||
switch true {
|
||||
case credentials.Connector != nil:
|
||||
db, err := credentials.Connector()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
handle = sqlx.NewDb(db, credentials.DriverName)
|
||||
default:
|
||||
handle, err = sqlx.Connect(credentials.DriverName, credentials.DSN)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
r.instances[name] = &DB{
|
||||
handle,
|
||||
context.Background(),
|
||||
0,
|
||||
nil,
|
||||
&sql.TxOptions{
|
||||
ReadOnly: false,
|
||||
},
|
||||
logger.Silent{},
|
||||
}
|
||||
return r.instances[name], nil
|
||||
}
|
||||
return nil, fmt.Errorf("No configuration found for database: %v", names)
|
||||
}
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory/logger"
|
||||
)
|
||||
|
||||
// DatabaseConnectionOptions is a configuration struct for connection retry
|
||||
type DatabaseConnectionOptions struct {
|
||||
DSN string
|
||||
DriverName string
|
||||
Logger string
|
||||
|
||||
Retries int
|
||||
RetryTimeout time.Duration
|
||||
ConnectTimeout time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
dsnMasker = regexp.MustCompile("(.)(?:.*)(.):(.)(?:.*)(.)@")
|
||||
)
|
||||
|
||||
func (df *DatabaseFactory) TryToConnect(ctx context.Context, name string, options *DatabaseConnectionOptions) (db *DB, err error) {
|
||||
df.Add(name, DatabaseCredential{
|
||||
DSN: options.DSN,
|
||||
DriverName: options.DriverName,
|
||||
})
|
||||
|
||||
var (
|
||||
connErrCh = make(chan error, 1)
|
||||
)
|
||||
|
||||
// We'll not add this to the general log because we do not want to carry it with us for every query.
|
||||
dsnField := fmt.Sprintf("dsn=%s", dsnMasker.ReplaceAllString(options.DSN, "$1****$2:$3****$4@"))
|
||||
|
||||
defer close(connErrCh)
|
||||
|
||||
log.Println("connecting to database", dsnField)
|
||||
|
||||
go func() {
|
||||
var (
|
||||
try = 0
|
||||
)
|
||||
|
||||
for {
|
||||
try++
|
||||
|
||||
if options.Retries <= try {
|
||||
err = errors.Errorf("could not connect to %q, in %d tries", name, try)
|
||||
return
|
||||
}
|
||||
|
||||
db, err = df.Get(name)
|
||||
if err != nil {
|
||||
log.Println(
|
||||
"could not connect to the database",
|
||||
err,
|
||||
dsnField,
|
||||
fmt.Sprintf("try=%d", try),
|
||||
)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Forced break
|
||||
break
|
||||
case <-time.After(options.RetryTimeout):
|
||||
// Wait before next try
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
connErrCh <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err = <-connErrCh:
|
||||
break
|
||||
case <-time.After(options.ConnectTimeout):
|
||||
// Wait before next try
|
||||
return nil, errors.Errorf("db init for %q timed out", name)
|
||||
case <-ctx.Done():
|
||||
return nil, errors.Errorf("db connection for %q cancelled", name)
|
||||
}
|
||||
|
||||
switch options.Logger {
|
||||
case "stdout":
|
||||
db.SetLogger(logger.Default{})
|
||||
default:
|
||||
db.SetLogger(logger.Silent{})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
Vendored
+1
-1
@@ -155,7 +155,7 @@ github.com/steinfletcher/apitest-jsonpath
|
||||
# github.com/stretchr/testify v1.5.1
|
||||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/require
|
||||
# github.com/titpetric/factory v0.0.0-20190806200833-ae4b02b9e034
|
||||
# github.com/titpetric/factory v0.0.0-20190828134837-8466c9bef13f
|
||||
github.com/titpetric/factory
|
||||
github.com/titpetric/factory/logger
|
||||
github.com/titpetric/factory/resputil
|
||||
|
||||
Reference in New Issue
Block a user