Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add unix-socket-path to instance command arguments. #1623

Merged
merged 9 commits into from
Jan 31, 2023
27 changes: 27 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ Instance Level Configuration
my-project:us-central1:my-db-server \
'my-project:us-central1:my-other-server?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 @@ -543,13 +553,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 {
enocom marked this conversation as resolved.
Show resolved Hide resolved
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 Down Expand Up @@ -585,6 +605,13 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error {
ic.UnixSocket = u[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]
}

ic.IAMAuthN, err = parseBoolOpt(q, "auto-iam-authn")
if err != nil {
return err
Expand Down
21 changes: 21 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ func TestNewCommandArguments(t *testing.T) {
}},
}),
},
{
desc: "using the unix socket path query param",
args: []string{"proj:region:inst?unix-socket-path=/path/to/file"},
want: withDefaults(&proxy.Config{
Instances: []proxy.InstanceConnConfig{{
UnixSocketPath: "/path/to/file",
}},
}),
},
{
desc: "using the iam authn login flag",
args: []string{"--auto-iam-authn", "proj:region:inst"},
Expand Down Expand Up @@ -959,14 +968,26 @@ func TestNewCommandWithErrors(t *testing.T) {
desc: "using the unix socket flag with port",
args: []string{"-u", "/path/to/dir/", "-p", "5432", "proj:region:inst"},
},
{
desc: "using the unix socket and unix-socket-path",
args: []string{"proj:region:inst?unix-socket=/path&unix-socket-path=/another/path"},
},
{
desc: "using the unix socket and addr query params",
args: []string{"proj:region:inst?unix-socket=/path&address=127.0.0.1"},
},
{
desc: "using the unix socket path and addr query params",
args: []string{"proj:region:inst?unix-socket-path=/path&address=127.0.0.1"},
},
{
desc: "using the unix socket and port query params",
args: []string{"proj:region:inst?unix-socket=/path&port=5000"},
},
{
desc: "using the unix socket path and port query params",
args: []string{"proj:region:inst?unix-socket-path=/path&port=5000"},
},
{
desc: "when the iam authn login query param contains multiple values",
args: []string{"proj:region:inst?auto-iam-authn=true&auto-iam-authn=false"},
Expand Down
86 changes: 66 additions & 20 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"
"regexp"
"strings"
"sync"
Expand Down Expand Up @@ -83,9 +84,17 @@ type InstanceConnConfig struct {
// Port is the port on which to bind a listener for the instance.
Port int
// UnixSocket is the directory where a Unix socket will be created,
// connected to the Cloud SQL instance. If set, takes precedence over Addr
// connected to the Cloud SQL instance. The full path to the socket will be
// UnixSocket + os.PathSeparator + Name. 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. If this is a Postgres database, the proxy will ensure that
// 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
// IAMAuthN enables automatic IAM DB Authentication for the instance.
// Postgres-only. If it is nil, the value was not specified.
IAMAuthN *bool
Expand Down Expand Up @@ -703,6 +712,7 @@ func newSocketMount(ctx context.Context, conf *Config, pc *portConfig, inst Inst
network string
// address is either a TCP host port, or a Unix socket
address string
err error
)
// IF
// a global Unix socket directory is NOT set AND
Expand All @@ -714,7 +724,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 @@ -737,26 +747,10 @@ func newSocketMount(ctx context.Context, conf *Config, pc *portConfig, inst Inst
} else {
network = "unix"

dir := conf.UnixSocket
if dir == "" {
dir = inst.UnixSocket
}
if _, err := os.Stat(dir); err != nil {
address, err = newUnixSocketMount(inst, conf.UnixSocket, strings.HasPrefix(version, "POSTGRES"))
if err != nil {
return nil, err
}
address = UnixAddress(dir, inst.Name)
// When setting up a listener for Postgres, create address as a
// directory, and use the Postgres-specific socket name
// .s.PGSQL.5432.
if strings.HasPrefix(version, "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 nil, err
}
}
address = UnixAddress(address, ".s.PGSQL.5432")
}
}

lc := net.ListenConfig{KeepAlive: 30 * time.Second}
Expand All @@ -775,6 +769,58 @@ 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
)

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 = UnixAddress(dir, inst.Name)
}

// 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
49 changes: 49 additions & 0 deletions internal/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"io"
"net"
"os"
"path"
"path/filepath"
"strings"
"sync"
Expand Down Expand Up @@ -114,6 +115,9 @@ func createTempDir(t *testing.T) (string, func()) {
func TestClientInitialization(t *testing.T) {
ctx := context.Background()
testDir, cleanup := createTempDir(t)
testUnixSocketPath := path.Join(testDir, "db")
testUnixSocketPathPg := path.Join(testDir, "db", ".s.PGSQL.5432")

defer cleanup()

tcs := []struct {
Expand Down Expand Up @@ -257,6 +261,51 @@ func TestClientInitialization(t *testing.T) {
filepath.Join(testDir, pg, ".s.PGSQL.5432"),
},
},
{
desc: "with a Unix socket path per instance",
enocom marked this conversation as resolved.
Show resolved Hide resolved
in: &proxy.Config{
Instances: []proxy.InstanceConnConfig{
{Name: mysql, UnixSocketPath: testUnixSocketPath},
},
},
wantUnixAddrs: []string{
filepath.Join(testUnixSocketPath),
},
},
{
desc: "with a Unix socket path overriding Unix socket",
in: &proxy.Config{
UnixSocket: testDir,
Instances: []proxy.InstanceConnConfig{
{Name: mysql, UnixSocketPath: testUnixSocketPath},
},
},
wantUnixAddrs: []string{
filepath.Join(testUnixSocketPath),
},
},
{
desc: "with a Unix socket path per pg instance",
in: &proxy.Config{
Instances: []proxy.InstanceConnConfig{
{Name: pg, 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: pg, UnixSocketPath: testUnixSocketPathPg},
},
},
wantUnixAddrs: []string{
filepath.Join(testUnixSocketPathPg),
},
},
}

for _, tc := range tcs {
Expand Down