Skip to content

Commit b1b6063

Browse files
Support connection service file and service names
The whole point of supporting this can best be said by directly quoting the PostgreSQL manual: > The connection service file allows libpq connection parameters to be > associated with a single service name. That service name can then be > specified by a libpq connection, and the associated settings will be > used. This allows connection parameters to be modified without > requiring a recompile of the libpq application. The service name can > also be specified using the PGSERVICE environment variable. source: https://www.postgresql.org/docs/current/libpq-pgservice.html Fixes #538
1 parent 2ff3cb3 commit b1b6063

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

conn.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1863,7 +1863,9 @@ func parseEnviron(env []string) (out map[string]string) {
18631863
accrue("user")
18641864
case "PGPASSWORD":
18651865
accrue("password")
1866-
case "PGSERVICE", "PGSERVICEFILE", "PGREALM":
1866+
case "PGSERVICE":
1867+
accrue("service")
1868+
case "PGREALM":
18671869
unsupported()
18681870
case "PGOPTIONS":
18691871
accrue("options")
@@ -1901,6 +1903,63 @@ func parseEnviron(env []string) (out map[string]string) {
19011903
return out
19021904
}
19031905

1906+
// parseServiceFile parses the options from a service file and adds them to the values.
1907+
//
1908+
// The parsing code is based on parseServiceInfo from libpq's fe-connect.c
1909+
func parseServiceFile(service string, o values) error {
1910+
filename := os.Getenv("PGSERVICEFILE")
1911+
if filename == "" {
1912+
// XXX this code doesn't work on Windows where the default filename is
1913+
// XXX %APPDATA%\postgresql\pgpass.conf
1914+
// Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470
1915+
userHome := os.Getenv("HOME")
1916+
if userHome == "" {
1917+
user, err := user.Current()
1918+
if err != nil {
1919+
return err
1920+
}
1921+
userHome = user.HomeDir
1922+
}
1923+
filename = filepath.Join(userHome, ".pg_service.conf")
1924+
}
1925+
1926+
file, err := os.Open(filename)
1927+
if err != nil {
1928+
return err
1929+
}
1930+
defer file.Close()
1931+
1932+
scanner := bufio.NewScanner(file)
1933+
for scanner.Scan() {
1934+
line := strings.TrimSpace(scanner.Text())
1935+
1936+
// once we find the header of our section, we can start reading
1937+
if line == fmt.Sprintf("[%s]", service) {
1938+
for scanner.Scan() {
1939+
line = strings.TrimSpace(scanner.Text())
1940+
// once we find the next section, we're done
1941+
if strings.HasPrefix(line, "[") {
1942+
return nil
1943+
} else if line != "" {
1944+
if err := parseOpts(line, o); err != nil {
1945+
return err
1946+
}
1947+
}
1948+
}
1949+
// EOF means we're done
1950+
return nil
1951+
}
1952+
}
1953+
1954+
if err := scanner.Err(); err != nil {
1955+
return err
1956+
}
1957+
1958+
// if we end up here, we didn't find the service that was explicitly provided
1959+
// that seems reason enough to give up
1960+
panic(fmt.Sprintf(`definition of service "%s" not found`, service))
1961+
}
1962+
19041963
// isUTF8 returns whether name is a fuzzy variation of the string "UTF-8".
19051964
func isUTF8(name string) bool {
19061965
// Recognize all sorts of silly things as "UTF-8", like Postgres does

connector.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func NewConnector(dsn string) (*Connector, error) {
4747
//
4848
// * Very low precedence defaults applied in every situation
4949
// * Environment variables
50+
// * Service name variables
5051
// * Explicitly passed connection information
5152
o["host"] = "localhost"
5253
o["port"] = "5432"
@@ -68,6 +69,24 @@ func NewConnector(dsn string) (*Connector, error) {
6869
return nil, err
6970
}
7071

72+
// whenever a service is specified, we will need to parse the connection service file
73+
// and override the defaults with the parameters specified for that service.
74+
// See https://www.postgresql.org/docs/current/libpq-pgservice.html
75+
if service, ok := o["service"]; ok {
76+
if err := parseServiceFile(service, o); err != nil {
77+
return nil, err
78+
}
79+
80+
// By overwriting the options with the service parameters we may have masked some
81+
// explicitly passed connection information, e.g. "service=staging user=read_only".
82+
// By repeating the parseOpts we overcome this issue.
83+
if err := parseOpts(dsn, o); err != nil {
84+
return nil, err
85+
}
86+
// "service" itself should not be passed down as a connection parameter
87+
delete(o, "service")
88+
}
89+
7190
// Use the "fallback" application name if necessary
7291
if fallback, ok := o["fallback_application_name"]; ok {
7392
if _, ok := o["application_name"]; !ok {

0 commit comments

Comments
 (0)