Skip to content

Commit 3b9c90b

Browse files
authored
Merge pull request #53 from Icinga/redis-database-human-representation
db.DB/redis.Client: Details in Address for GetAddr
2 parents a82c397 + 1bcb8e0 commit 3b9c90b

File tree

4 files changed

+269
-5
lines changed

4 files changed

+269
-5
lines changed

database/db.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/lib/pq"
1919
"github.com/pkg/errors"
2020
"go.uber.org/zap"
21+
"go.uber.org/zap/zapcore"
2122
"golang.org/x/sync/errgroup"
2223
"golang.org/x/sync/semaphore"
2324
"net"
@@ -108,13 +109,15 @@ func NewDbFromConfig(c *Config, logger *logging.Logger, connectorCallbacks Retry
108109
if utils.IsUnixAddr(c.Host) {
109110
config.Net = "unix"
110111
config.Addr = c.Host
112+
addr = "(" + config.Addr + ")"
111113
} else {
112114
config.Net = "tcp"
113115
port := c.Port
114116
if port == 0 {
115117
port = 3306
116118
}
117119
config.Addr = net.JoinHostPort(c.Host, fmt.Sprint(port))
120+
addr = config.Addr
118121
}
119122

120123
config.DBName = c.Database
@@ -150,7 +153,6 @@ func NewDbFromConfig(c *Config, logger *logging.Logger, connectorCallbacks Retry
150153
return unsafeSetSessionVariableIfExists(ctx, conn, "wsrep_sync_wait", fmt.Sprint(c.Options.WsrepSyncWait))
151154
}
152155

153-
addr = config.Addr
154156
db = sqlx.NewDb(sql.OpenDB(NewConnector(connector, logger, connectorCallbacks)), MySQL)
155157
case "pgsql":
156158
uri := &url.URL{
@@ -208,12 +210,23 @@ func NewDbFromConfig(c *Config, logger *logging.Logger, connectorCallbacks Retry
208210
return nil, errors.Wrap(err, "can't open pgsql database")
209211
}
210212

211-
addr = utils.JoinHostPort(c.Host, port)
213+
if utils.IsUnixAddr(c.Host) {
214+
// https://www.postgresql.org/docs/17/runtime-config-connection.html#GUC-UNIX-SOCKET-DIRECTORIES
215+
addr = fmt.Sprintf("(%s/.s.PGSQL.%d)", strings.TrimRight(c.Host, "/"), port)
216+
} else {
217+
addr = utils.JoinHostPort(c.Host, port)
218+
}
212219
db = sqlx.NewDb(sql.OpenDB(NewConnector(connector, logger, connectorCallbacks)), PostgreSQL)
213220
default:
214221
return nil, unknownDbType(c.Type)
215222
}
216223

224+
if c.TlsOptions.Enable {
225+
addr = fmt.Sprintf("%s+tls://%s@%s/%s", c.Type, c.User, addr, c.Database)
226+
} else {
227+
addr = fmt.Sprintf("%s://%s@%s/%s", c.Type, c.User, addr, c.Database)
228+
}
229+
217230
db.SetMaxIdleConns(c.Options.MaxConnections / 3)
218231
db.SetMaxOpenConns(c.Options.MaxConnections)
219232

@@ -229,11 +242,22 @@ func NewDbFromConfig(c *Config, logger *logging.Logger, connectorCallbacks Retry
229242
}, nil
230243
}
231244

232-
// GetAddr returns the database host:port or Unix socket address.
245+
// GetAddr returns a URI-like database connection string.
246+
//
247+
// It has the following syntax:
248+
//
249+
// type[+tls]://user@host[:port]/database
233250
func (db *DB) GetAddr() string {
234251
return db.addr
235252
}
236253

254+
// MarshalLogObject implements [zapcore.ObjectMarshaler], adding the database address [DB.GetAddr] to each log message.
255+
func (db *DB) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
256+
encoder.AddString("database_address", db.GetAddr())
257+
258+
return nil
259+
}
260+
237261
// BuildColumns returns all columns of the given struct.
238262
func (db *DB) BuildColumns(subject interface{}) []string {
239263
return slices.Clone(db.columnMap.Columns(subject))

database/db_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package database
2+
3+
import (
4+
"github.com/icinga/icinga-go-library/config"
5+
"github.com/icinga/icinga-go-library/logging"
6+
"github.com/stretchr/testify/require"
7+
"go.uber.org/zap/zaptest"
8+
"testing"
9+
)
10+
11+
func TestNewDbFromConfig_GetAddr(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
conf *Config
15+
addr string
16+
}{
17+
{
18+
name: "mysql-simple",
19+
conf: &Config{
20+
Type: "mysql",
21+
Host: "example.com",
22+
Database: "db",
23+
User: "user",
24+
},
25+
addr: "mysql://user@example.com:3306/db",
26+
},
27+
{
28+
name: "mysql-custom-port",
29+
conf: &Config{
30+
Type: "mysql",
31+
Host: "example.com",
32+
Port: 1234,
33+
Database: "db",
34+
User: "user",
35+
},
36+
addr: "mysql://user@example.com:1234/db",
37+
},
38+
{
39+
name: "mysql-tls",
40+
conf: &Config{
41+
Type: "mysql",
42+
Host: "example.com",
43+
Database: "db",
44+
User: "user",
45+
TlsOptions: config.TLS{Enable: true},
46+
},
47+
addr: "mysql+tls://user@example.com:3306/db",
48+
},
49+
{
50+
name: "mysql-unix-domain-socket",
51+
conf: &Config{
52+
Type: "mysql",
53+
Host: "/var/empty/mysql.sock",
54+
Database: "db",
55+
User: "user",
56+
},
57+
addr: "mysql://user@(/var/empty/mysql.sock)/db",
58+
},
59+
{
60+
name: "pgsql-simple",
61+
conf: &Config{
62+
Type: "pgsql",
63+
Host: "example.com",
64+
Database: "db",
65+
User: "user",
66+
},
67+
addr: "pgsql://user@example.com:5432/db",
68+
},
69+
{
70+
name: "pgsql-custom-port",
71+
conf: &Config{
72+
Type: "pgsql",
73+
Host: "example.com",
74+
Port: 1234,
75+
Database: "db",
76+
User: "user",
77+
},
78+
addr: "pgsql://user@example.com:1234/db",
79+
},
80+
{
81+
name: "pgsql-tls",
82+
conf: &Config{
83+
Type: "pgsql",
84+
Host: "example.com",
85+
Database: "db",
86+
User: "user",
87+
TlsOptions: config.TLS{Enable: true},
88+
},
89+
addr: "pgsql+tls://user@example.com:5432/db",
90+
},
91+
{
92+
name: "pgsql-unix-domain-socket",
93+
conf: &Config{
94+
Type: "pgsql",
95+
Host: "/var/empty/pgsql",
96+
Database: "db",
97+
User: "user",
98+
},
99+
addr: "pgsql://user@(/var/empty/pgsql/.s.PGSQL.5432)/db",
100+
},
101+
{
102+
name: "pgsql-unix-domain-socket-custom-port",
103+
conf: &Config{
104+
Type: "pgsql",
105+
Host: "/var/empty/pgsql",
106+
Port: 1234,
107+
Database: "db",
108+
User: "user",
109+
},
110+
addr: "pgsql://user@(/var/empty/pgsql/.s.PGSQL.1234)/db",
111+
},
112+
}
113+
114+
for _, test := range tests {
115+
t.Run(test.name, func(t *testing.T) {
116+
db, err := NewDbFromConfig(
117+
test.conf,
118+
logging.NewLogger(zaptest.NewLogger(t).Sugar(), 0),
119+
RetryConnectorCallbacks{})
120+
require.NoError(t, err)
121+
require.Equal(t, test.addr, db.GetAddr())
122+
})
123+
}
124+
}

redis/client.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/pkg/errors"
1414
"github.com/redis/go-redis/v9"
1515
"go.uber.org/zap"
16+
"go.uber.org/zap/zapcore"
1617
"golang.org/x/sync/errgroup"
1718
"golang.org/x/sync/semaphore"
1819
"net"
@@ -79,9 +80,37 @@ func NewClientFromConfig(c *Config, logger *logging.Logger) (*Client, error) {
7980
return NewClient(redis.NewClient(options), logger, &c.Options), nil
8081
}
8182

82-
// GetAddr returns the Redis host:port or Unix socket address.
83+
// GetAddr returns a URI-like Redis connection string.
84+
//
85+
// It has the following syntax:
86+
//
87+
// redis[+tls]://user@host[:port]/database
8388
func (c *Client) GetAddr() string {
84-
return c.Client.Options().Addr
89+
description := "redis"
90+
if c.Client.Options().TLSConfig != nil {
91+
description += "+tls"
92+
}
93+
description += "://"
94+
if username := c.Client.Options().Username; username != "" {
95+
description += username + "@"
96+
}
97+
if utils.IsUnixAddr(c.Client.Options().Addr) {
98+
description += "(" + c.Client.Options().Addr + ")"
99+
} else {
100+
description += c.Client.Options().Addr
101+
}
102+
if db := c.Client.Options().DB; db != 0 {
103+
description += fmt.Sprintf("/%d", db)
104+
}
105+
106+
return description
107+
}
108+
109+
// MarshalLogObject implements [zapcore.ObjectMarshaler], adding the redis address [Client.GetAddr] to each log message.
110+
func (c *Client) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
111+
encoder.AddString("redis_address", c.GetAddr())
112+
113+
return nil
85114
}
86115

87116
// HPair defines Redis hashes field-value pairs.

redis/client_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package redis
2+
3+
import (
4+
"github.com/icinga/icinga-go-library/config"
5+
"github.com/icinga/icinga-go-library/logging"
6+
"github.com/stretchr/testify/require"
7+
"go.uber.org/zap/zaptest"
8+
"testing"
9+
)
10+
11+
func TestNewClientFromConfig_GetAddr(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
conf *Config
15+
addr string
16+
}{
17+
{
18+
name: "redis-simple",
19+
conf: &Config{
20+
Host: "example.com",
21+
},
22+
addr: "redis://example.com:6379",
23+
},
24+
{
25+
name: "redis-custom-port",
26+
conf: &Config{
27+
Host: "example.com",
28+
Port: 6380,
29+
},
30+
addr: "redis://example.com:6380",
31+
},
32+
{
33+
name: "redis-acl",
34+
conf: &Config{
35+
Host: "example.com",
36+
Username: "user",
37+
Password: "pass",
38+
},
39+
addr: "redis://user@example.com:6379",
40+
},
41+
{
42+
name: "redis-custom-database",
43+
conf: &Config{
44+
Host: "example.com",
45+
Database: 23,
46+
},
47+
addr: "redis://example.com:6379/23",
48+
},
49+
{
50+
name: "redis-tls",
51+
conf: &Config{
52+
Host: "example.com",
53+
TlsOptions: config.TLS{Enable: true},
54+
},
55+
addr: "redis+tls://example.com:6379",
56+
},
57+
{
58+
name: "redis-with-everything",
59+
conf: &Config{
60+
Host: "example.com",
61+
Port: 6380,
62+
Username: "user",
63+
Password: "pass",
64+
Database: 23,
65+
TlsOptions: config.TLS{Enable: true},
66+
},
67+
addr: "redis+tls://user@example.com:6380/23",
68+
},
69+
{
70+
name: "redis-unix-domain-socket",
71+
conf: &Config{
72+
Host: "/var/empty/redis.sock",
73+
},
74+
addr: "redis://(/var/empty/redis.sock)",
75+
},
76+
}
77+
78+
for _, test := range tests {
79+
t.Run(test.name, func(t *testing.T) {
80+
redis, err := NewClientFromConfig(
81+
test.conf,
82+
logging.NewLogger(zaptest.NewLogger(t).Sugar(), 0))
83+
require.NoError(t, err)
84+
require.Equal(t, test.addr, redis.GetAddr())
85+
})
86+
}
87+
}

0 commit comments

Comments
 (0)