Skip to content

Commit

Permalink
Prevent logging full dsn to std err
Browse files Browse the repository at this point in the history
  • Loading branch information
linxGnu committed Jul 4, 2022
1 parent 4064339 commit 407a439
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 2 deletions.
21 changes: 20 additions & 1 deletion balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"sync/atomic"
"time"

"github.com/go-sql-driver/mysql"
)

// database balancer and health checker.
Expand Down Expand Up @@ -74,7 +76,10 @@ func (c *balancer) get(shouldBalancing bool) *wrapper {
// failure make a db node become failure and auto health tracking
func (c *balancer) failure(w *wrapper, err error) {
if c.dbs.remove(w) { // remove this node
reportError(fmt.Sprintf("deactive connection:[%s] for health checking due to error", w.dsn), err)
reportError(
fmt.Sprintf("deactive connection:[%s] for health checking due to error", parseHostnameFromDSN(w.db.DriverName(), w.dsn)),
err,
)

select {
case <-c.ctx.Done():
Expand Down Expand Up @@ -111,3 +116,17 @@ func (c *balancer) healthChecker() {
func (c *balancer) destroy() {
c.cancel()
}

func parseHostnameFromDSN(driverName, dsn string) string {
switch driverName {
case "mysql":
if cf, err := mysql.ParseDSN(dsn); err == nil {
return fmt.Sprintf("%s(%s)", cf.Net, cf.Addr)
}
case "postgres":
if host, err := parsePostgresDSN(dsn); err == nil {
return host
}
}
return ""
}
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
module github.com/linxGnu/mssqlx

go 1.18

require (
github.com/go-sql-driver/mysql v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.6
github.com/stretchr/testify v1.7.1
github.com/stretchr/testify v1.8.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/mattn/go-sqlite3 v1.14.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
148 changes: 148 additions & 0 deletions pq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copy from: https://github.com/lib/pq//conn.go

package mssqlx

import (
"fmt"
"unicode"
)

// scanner implements a tokenizer for libpq-style option strings.
type scanner struct {
s []rune
i int
}

// newScanner returns a new scanner initialized with the option string s.
func newScanner(s string) *scanner {
return &scanner{[]rune(s), 0}
}

// Next returns the next rune.
// It returns 0, false if the end of the text has been reached.
func (s *scanner) Next() (rune, bool) {
if s.i >= len(s.s) {
return 0, false
}
r := s.s[s.i]
s.i++
return r, true
}

// SkipSpaces returns the next non-whitespace rune.
// It returns 0, false if the end of the text has been reached.
func (s *scanner) SkipSpaces() (rune, bool) {
r, ok := s.Next()
for unicode.IsSpace(r) && ok {
r, ok = s.Next()
}
return r, ok
}

// parseOpts parses the options from name and adds them to the values.
//
// The parsing code is based on conninfo_parse from libpq's fe-connect.c
func parseOpts(name string, o map[string]string) error {
s := newScanner(name)

for {
var (
keyRunes, valRunes []rune
r rune
ok bool
)

if r, ok = s.SkipSpaces(); !ok {
break
}

// Scan the key
for !unicode.IsSpace(r) && r != '=' {
keyRunes = append(keyRunes, r)
if r, ok = s.Next(); !ok {
break
}
}

// Skip any whitespace if we're not at the = yet
if r != '=' {
r, ok = s.SkipSpaces()
}

// The current character should be =
if r != '=' || !ok {
return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
}

// Skip any whitespace after the =
if r, ok = s.SkipSpaces(); !ok {
// If we reach the end here, the last value is just an empty string as per libpq.
o[string(keyRunes)] = ""
break
}

if r != '\'' {
for !unicode.IsSpace(r) {
if r == '\\' {
if r, ok = s.Next(); !ok {
return fmt.Errorf(`missing character after backslash`)
}
}
valRunes = append(valRunes, r)

if r, ok = s.Next(); !ok {
break
}
}
} else {
quote:
for {
if r, ok = s.Next(); !ok {
return fmt.Errorf(`unterminated quoted string literal in connection string`)
}
switch r {
case '\'':
break quote
case '\\':
r, _ = s.Next()
fallthrough
default:
valRunes = append(valRunes, r)
}
}
}

o[string(keyRunes)] = string(valRunes)
}

return nil
}

// converts a url to a connection string for driver.Open.
// Example:
//
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
//
// converts to:
//
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
//
// A minimal example:
//
// "postgres://"
//
// This will be blank, causing driver.Open to use all of the defaults
func parsePostgresDSN(dsn string) (string, error) {
meta := make(map[string]string)

if err := parseOpts(dsn, meta); err != nil {
return "", err
}

host, port := meta["host"], meta["port"]
if host == "" && port == "" {
return "", nil
}

return fmt.Sprintf("%s:%s", host, port), nil
}
28 changes: 28 additions & 0 deletions pq_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package mssqlx

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParseDSN(t *testing.T) {
require.Equal(t, "127.0.0.1:5555",
parseHostnameFromDSN("postgres", "host=127.0.0.1 port=5555 user=root password=password dbname=testdb sslmode=disable"))

require.Equal(t, "",
parseHostnameFromDSN("postgres", "a=b c=d"))

require.Equal(t, "",
parseHostnameFromDSN("postgres", "a:b"))

require.Equal(t, "tcp(172.17.0.2:3306)",
parseHostnameFromDSN("mysql", "user:password@(172.17.0.2:3306)/practice?charset=utf8mb4&interpolateParams=true"))

require.Equal(t, "udp(172.17.0.2:3306)",
parseHostnameFromDSN("mysql", "user:password@udp(172.17.0.2:3306)/practice?charset=utf8mb4&interpolateParams=true"))

require.Equal(t, "",
parseHostnameFromDSN("mysql", "a=b c=d"))

}

0 comments on commit 407a439

Please sign in to comment.