refactor postgres connection string building (#27723)
This patchset changes the connection string builder to use net.URL and the host/port parser to use the stdlib function for splitting host from port. It also adds a footnote about a potentially required portnumber for postgres UNIX sockets. Fixes: #24552
This commit is contained in:
parent
665d12cf84
commit
9b6e77c489
3 changed files with 47 additions and 30 deletions
|
@ -424,7 +424,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||||
## Database (`database`)
|
## Database (`database`)
|
||||||
|
|
||||||
- `DB_TYPE`: **mysql**: The database type in use \[mysql, postgres, mssql, sqlite3\].
|
- `DB_TYPE`: **mysql**: The database type in use \[mysql, postgres, mssql, sqlite3\].
|
||||||
- `HOST`: **127.0.0.1:3306**: Database host address and port or absolute path for unix socket \[mysql, postgres\] (ex: /var/run/mysqld/mysqld.sock).
|
- `HOST`: **127.0.0.1:3306**: Database host address and port or absolute path for unix socket \[mysql, postgres[^1]\] (ex: /var/run/mysqld/mysqld.sock).
|
||||||
- `NAME`: **gitea**: Database name.
|
- `NAME`: **gitea**: Database name.
|
||||||
- `USER`: **root**: Database username.
|
- `USER`: **root**: Database username.
|
||||||
- `PASSWD`: **_empty_**: Database user password. Use \`your password\` or """your password""" for quoting if you use special characters in the password.
|
- `PASSWD`: **_empty_**: Database user password. Use \`your password\` or """your password""" for quoting if you use special characters in the password.
|
||||||
|
@ -455,6 +455,8 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||||
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
|
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
|
||||||
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
|
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
|
||||||
|
|
||||||
|
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
|
||||||
|
|
||||||
Please see #8540 & #8273 for further discussion of the appropriate values for `MAX_OPEN_CONNS`, `MAX_IDLE_CONNS` & `CONN_MAX_LIFETIME` and their
|
Please see #8540 & #8273 for further discussion of the appropriate values for `MAX_OPEN_CONNS`, `MAX_IDLE_CONNS` & `CONN_MAX_LIFETIME` and their
|
||||||
relation to port exhaustion.
|
relation to port exhaustion.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ package setting
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -135,15 +136,18 @@ func DBConnStr() (string, error) {
|
||||||
// parsePostgreSQLHostPort parses given input in various forms defined in
|
// parsePostgreSQLHostPort parses given input in various forms defined in
|
||||||
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
// and returns proper host and port number.
|
// and returns proper host and port number.
|
||||||
func parsePostgreSQLHostPort(info string) (string, string) {
|
func parsePostgreSQLHostPort(info string) (host, port string) {
|
||||||
host, port := "127.0.0.1", "5432"
|
if h, p, err := net.SplitHostPort(info); err == nil {
|
||||||
if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
|
host, port = h, p
|
||||||
idx := strings.LastIndex(info, ":")
|
} else {
|
||||||
host = info[:idx]
|
// treat the "info" as "host", if it's an IPv6 address, remove the wrapper
|
||||||
port = info[idx+1:]
|
|
||||||
} else if len(info) > 0 {
|
|
||||||
host = info
|
host = info
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||||
|
host = host[1 : len(host)-1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set fallback values
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
}
|
}
|
||||||
|
@ -155,14 +159,22 @@ func parsePostgreSQLHostPort(info string) (string, string) {
|
||||||
|
|
||||||
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) {
|
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) {
|
||||||
host, port := parsePostgreSQLHostPort(dbHost)
|
host, port := parsePostgreSQLHostPort(dbHost)
|
||||||
if host[0] == '/' { // looks like a unix socket
|
connURL := url.URL{
|
||||||
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
|
Scheme: "postgres",
|
||||||
url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbsslMode, host)
|
User: url.UserPassword(dbUser, dbPasswd),
|
||||||
} else {
|
Host: net.JoinHostPort(host, port),
|
||||||
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
|
Path: dbName,
|
||||||
url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbsslMode)
|
OmitHost: false,
|
||||||
|
RawQuery: dbParam,
|
||||||
}
|
}
|
||||||
return connStr
|
query := connURL.Query()
|
||||||
|
if dbHost[0] == '/' { // looks like a unix socket
|
||||||
|
query.Add("host", dbHost)
|
||||||
|
connURL.Host = ":" + port
|
||||||
|
}
|
||||||
|
query.Set("sslmode", dbsslMode)
|
||||||
|
connURL.RawQuery = query.Encode()
|
||||||
|
return connURL.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseMSSQLHostPort splits the host into host and port
|
// ParseMSSQLHostPort splits the host into host and port
|
||||||
|
|
|
@ -10,46 +10,49 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_parsePostgreSQLHostPort(t *testing.T) {
|
func Test_parsePostgreSQLHostPort(t *testing.T) {
|
||||||
tests := []struct {
|
tests := map[string]struct {
|
||||||
HostPort string
|
HostPort string
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
}{
|
}{
|
||||||
{
|
"host-port": {
|
||||||
HostPort: "127.0.0.1:1234",
|
HostPort: "127.0.0.1:1234",
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: "1234",
|
Port: "1234",
|
||||||
},
|
},
|
||||||
{
|
"no-port": {
|
||||||
HostPort: "127.0.0.1",
|
HostPort: "127.0.0.1",
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: "5432",
|
Port: "5432",
|
||||||
},
|
},
|
||||||
{
|
"ipv6-port": {
|
||||||
HostPort: "[::1]:1234",
|
HostPort: "[::1]:1234",
|
||||||
Host: "[::1]",
|
Host: "::1",
|
||||||
Port: "1234",
|
Port: "1234",
|
||||||
},
|
},
|
||||||
{
|
"ipv6-no-port": {
|
||||||
HostPort: "[::1]",
|
HostPort: "[::1]",
|
||||||
Host: "[::1]",
|
Host: "::1",
|
||||||
Port: "5432",
|
Port: "5432",
|
||||||
},
|
},
|
||||||
{
|
"unix-socket": {
|
||||||
HostPort: "/tmp/pg.sock:1234",
|
HostPort: "/tmp/pg.sock:1234",
|
||||||
Host: "/tmp/pg.sock",
|
Host: "/tmp/pg.sock",
|
||||||
Port: "1234",
|
Port: "1234",
|
||||||
},
|
},
|
||||||
{
|
"unix-socket-no-port": {
|
||||||
HostPort: "/tmp/pg.sock",
|
HostPort: "/tmp/pg.sock",
|
||||||
Host: "/tmp/pg.sock",
|
Host: "/tmp/pg.sock",
|
||||||
Port: "5432",
|
Port: "5432",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for k, test := range tests {
|
||||||
host, port := parsePostgreSQLHostPort(test.HostPort)
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, test.Host, host)
|
t.Log(test.HostPort)
|
||||||
assert.Equal(t, test.Port, port)
|
host, port := parsePostgreSQLHostPort(test.HostPort)
|
||||||
|
assert.Equal(t, test.Host, host)
|
||||||
|
assert.Equal(t, test.Port, port)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +75,7 @@ func Test_getPostgreSQLConnectionString(t *testing.T) {
|
||||||
Name: "gitea",
|
Name: "gitea",
|
||||||
Param: "",
|
Param: "",
|
||||||
SSLMode: "false",
|
SSLMode: "false",
|
||||||
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/giteasslmode=false&host=/tmp/pg.sock",
|
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/gitea?host=%2Ftmp%2Fpg.sock&sslmode=false",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
|
@ -82,7 +85,7 @@ func Test_getPostgreSQLConnectionString(t *testing.T) {
|
||||||
Name: "gitea",
|
Name: "gitea",
|
||||||
Param: "",
|
Param: "",
|
||||||
SSLMode: "true",
|
SSLMode: "true",
|
||||||
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/giteasslmode=true",
|
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/gitea?sslmode=true",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue