3
0

Update github.com/titpetric/factory

This commit is contained in:
Denis Arh
2020-06-19 12:43:03 +02:00
parent 2f064020f0
commit c2e568f22e
7 changed files with 172 additions and 51 deletions
+1 -1
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
View File
@@ -1,7 +1,4 @@
.PHONY: all test
.PHONY: all
all:
drone exec
test:
gotest ./... -v -cover
+59 -42
View File
@@ -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
View File
@@ -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
}
+1 -1
View File
@@ -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