Skip to content

Commit

Permalink
feat: add support for unix-socket-path instance parameter. (#251)
Browse files Browse the repository at this point in the history
This adds a new parameter unix-socket-path to instances to specify the precise unix socket path.

For example, this command line:

    $ alloydb-auth-proxy "projects/proj/locations/region/clusters/clust/instances/inst?unix-socket-path=/var/cloudsql/mydb"

Will open a unix socket to the database at the path /var/cloudsql/mydb. This is different than the 
existing parameter unix-socket which automatically appends the instance name to the path.

Fixes GoogleCloudPlatform/cloud-sql-proxy#1573
  • Loading branch information
hessjcg authored Jan 31, 2023
1 parent a2413f2 commit 3c17cfa
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 15 deletions.
27 changes: 27 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ Instance Level Configuration
'projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE1' \
'projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE2?address=0.0.0.0&port=7000'
When necessary, you may specify the full path to a Unix socket. Set the
unix-socket-path query parameter to the absolute path of the Unix socket for
the database instance. The parent directory of the unix-socket-path must
exist when the proxy starts or else socket creation will fail. For Postgres
instances, the proxy will ensure that the last path element is
'.s.PGSQL.5432' appending it if necessary. For example,
./cloud-sql-proxy \
'my-project:us-central1:my-db-server?unix-socket-path=/path/to/socket'
Health checks
When enabling the --health-check flag, the proxy will start an HTTP server
Expand Down Expand Up @@ -541,13 +551,23 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error {
a, aok := q["address"]
p, pok := q["port"]
u, uok := q["unix-socket"]
up, upok := q["unix-socket-path"]

if aok && uok {
return newBadCommandError("cannot specify both address and unix-socket query params")
}
if pok && uok {
return newBadCommandError("cannot specify both port and unix-socket query params")
}
if aok && upok {
return newBadCommandError("cannot specify both address and unix-socket-path query params")
}
if pok && upok {
return newBadCommandError("cannot specify both port and unix-socket-path query params")
}
if uok && upok {
return newBadCommandError("cannot specify both unix-socket-path and unix-socket query params")
}

if aok {
if len(a) != 1 {
Expand All @@ -562,6 +582,13 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error {
ic.Addr = a[0]
}

if upok {
if len(up) != 1 {
return newBadCommandError(fmt.Sprintf("unix-socket-path query param should be only one value: %q", a))
}
ic.UnixSocketPath = up[0]
}

if pok {
if len(p) != 1 {
return newBadCommandError(fmt.Sprintf("port query param should be only one value: %q", a))
Expand Down
21 changes: 21 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ func TestNewCommandArguments(t *testing.T) {
}},
}),
},
{
desc: "using the unix socket path query param",
args: []string{"projects/proj/locations/region/clusters/clust/instances/inst?unix-socket-path=/path/to/file"},
want: withDefaults(&proxy.Config{
Instances: []proxy.InstanceConnConfig{{
UnixSocketPath: "/path/to/file",
}},
}),
},
{
desc: "using the max connections flag",
args: []string{"--max-connections", "1", "projects/proj/locations/region/clusters/clust/instances/inst"},
Expand Down Expand Up @@ -773,6 +782,18 @@ func TestNewCommandWithErrors(t *testing.T) {
desc: "using the unix socket flag with port",
args: []string{"-u", "/path/to/dir/", "-p", "5432", "projects/proj/locations/region/clusters/clust/instances/inst"},
},
{
desc: "using the unix socket and unix-socket-path",
args: []string{"projects/proj/locations/region/clusters/clust/instances/inst?unix-socket=/path&unix-socket-path=/another/path"},
},
{
desc: "using the unix socket path and addr query params",
args: []string{"projects/proj/locations/region/clusters/clust/instances/inst?unix-socket-path=/path&address=127.0.0.1"},
},
{
desc: "using the unix socket path and port query params",
args: []string{"projects/proj/locations/region/clusters/clust/instances/inst?unix-socket-path=/path&port=5000"},
},
{
desc: "using the unix socket and addr query params",
args: []string{"projects/proj/locations/region/clusters/clust/instances/inst?unix-socket=/path&address=127.0.0.1"},
Expand Down
74 changes: 59 additions & 15 deletions internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"io"
"net"
"os"
"path"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -48,6 +49,13 @@ type InstanceConnConfig struct {
// connected to the AlloyDB instance. If set, takes precedence over Addr
// and Port.
UnixSocket string
// UnixSocketPath is the path where a Unix socket will be created,
// connected to the Cloud SQL instance. The full path to the socket will be
// UnixSocketPath. Because this is a Postgres database, the proxy will ensure
// the last path element is `.s.PGSQL.5432`, appending this path element if
// necessary. If set, UnixSocketPath takes precedence over UnixSocket, Addr
// and Port.
UnixSocketPath string
}

// Config contains all the configuration provided by the caller.
Expand Down Expand Up @@ -648,6 +656,7 @@ func newSocketMount(ctx context.Context, conf *Config, pc *portConfig, inst Inst
// address is either a TCP host port, or a Unix socket
address string
)

// IF
// a global Unix socket directory is NOT set AND
// an instance-level Unix socket is NOT set
Expand All @@ -658,7 +667,7 @@ func newSocketMount(ctx context.Context, conf *Config, pc *portConfig, inst Inst
// instance)
// use a TCP listener.
// Otherwise, use a Unix socket.
if (conf.UnixSocket == "" && inst.UnixSocket == "") ||
if (conf.UnixSocket == "" && inst.UnixSocket == "" && inst.UnixSocketPath == "") ||
(inst.Addr != "" || inst.Port != 0) {
network = "tcp"

Expand All @@ -678,23 +687,10 @@ func newSocketMount(ctx context.Context, conf *Config, pc *portConfig, inst Inst
address = net.JoinHostPort(a, fmt.Sprint(np))
} else {
network = "unix"

dir := conf.UnixSocket
if dir == "" {
dir = inst.UnixSocket
}
ud, err := UnixSocketDir(dir, inst.Name)
address, err = newUnixSocketMount(inst, conf.UnixSocket, true)
if err != nil {
return nil, err
}
// Create the parent directory that will hold the socket.
if _, err := os.Stat(ud); err != nil {
if err = os.Mkdir(ud, 0777); err != nil {
return nil, err
}
}
// use the Postgres-specific socket name
address = filepath.Join(ud, ".s.PGSQL.5432")
}

lc := net.ListenConfig{KeepAlive: 30 * time.Second}
Expand All @@ -717,6 +713,54 @@ func newSocketMount(ctx context.Context, conf *Config, pc *portConfig, inst Inst
return m, nil
}

// newUnixSocketMount parses the configuration and returns the path to the unix
// socket, or an error if that path is not valid.
func newUnixSocketMount(inst InstanceConnConfig, unixSocketDir string, postgres bool) (string, error) {
var (
// the path to the unix socket
address string
// the parent directory of the unix socket
dir string
err error
)
if inst.UnixSocketPath != "" {
// When UnixSocketPath is set
address = inst.UnixSocketPath
// If UnixSocketPath ends .s.PGSQL.5432, remove it for consistency
if postgres && path.Base(address) == ".s.PGSQL.5432" {
address = path.Dir(address)
}
dir = path.Dir(address)
} else {
// When UnixSocket is set
dir = unixSocketDir
if dir == "" {
dir = inst.UnixSocket
}
address, err = UnixSocketDir(dir, inst.Name)
if err != nil {
return "", err
}
}
// if base directory does not exist, fail
if _, err := os.Stat(dir); err != nil {
return "", err
}
// When setting up a listener for Postgres, create address as a
// directory, and use the Postgres-specific socket name
// .s.PGSQL.5432.
if postgres {
// Make the directory only if it hasn't already been created.
if _, err := os.Stat(address); err != nil {
if err = os.Mkdir(address, 0777); err != nil {
return "", err
}
}
address = UnixAddress(address, ".s.PGSQL.5432")
}
return address, nil
}

func (s *socketMount) Addr() net.Addr {
return s.listener.Addr()
}
Expand Down
37 changes: 37 additions & 0 deletions internal/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"path"
"path/filepath"
"sync"
"testing"
Expand Down Expand Up @@ -103,6 +104,8 @@ func TestClientInitialization(t *testing.T) {
inst1 := "projects/proj/locations/region/clusters/clust/instances/inst1"
inst2 := "projects/proj/locations/region/clusters/clust/instances/inst2"
wantUnix := "proj.region.clust.inst1"
testUnixSocketPath := path.Join(testDir, "db")
testUnixSocketPathPg := path.Join(testDir, "db", ".s.PGSQL.5432")

tcs := []testCase{
{
Expand Down Expand Up @@ -207,6 +210,40 @@ func TestClientInitialization(t *testing.T) {
"127.0.0.1:5000",
},
},
{
desc: "with a Unix socket path overriding Unix socket",
in: &proxy.Config{
UnixSocket: testDir,
Instances: []proxy.InstanceConnConfig{
{Name: inst1, UnixSocketPath: testUnixSocketPath},
},
},
wantUnixAddrs: []string{
filepath.Join(testUnixSocketPathPg),
},
},
{
desc: "with a Unix socket path per pg instance",
in: &proxy.Config{
Instances: []proxy.InstanceConnConfig{
{Name: inst1, UnixSocketPath: testUnixSocketPath},
},
},
wantUnixAddrs: []string{
filepath.Join(testUnixSocketPathPg),
},
},
{
desc: "with a Unix socket path per pg instance and explicit pg path suffix",
in: &proxy.Config{
Instances: []proxy.InstanceConnConfig{
{Name: inst1, UnixSocketPath: testUnixSocketPathPg},
},
},
wantUnixAddrs: []string{
filepath.Join(testUnixSocketPathPg),
},
},
}
_, isFlex := os.LookupEnv("FLEX")
if !isFlex {
Expand Down

0 comments on commit 3c17cfa

Please sign in to comment.