@@ -19,6 +19,10 @@ import (
1919 "fmt"
2020 "io"
2121 "net"
22+ "os"
23+ "path/filepath"
24+ "regexp"
25+ "strings"
2226 "sync"
2327 "time"
2428
@@ -37,6 +41,10 @@ type InstanceConnConfig struct {
3741 Addr string
3842 // Port is the port on which to bind a listener for the instance.
3943 Port int
44+ // UnixSocket is the directory where a Unix socket will be created,
45+ // connected to the Cloud SQL instance. If set, takes precedence over Addr
46+ // and Port.
47+ UnixSocket string
4048}
4149
4250// Config contains all the configuration provided by the caller.
@@ -54,6 +62,10 @@ type Config struct {
5462 // increments from this value.
5563 Port int
5664
65+ // UnixSocket is the directory where Unix sockets will be created,
66+ // connected to any Instances. If set, takes precedence over Addr and Port.
67+ UnixSocket string
68+
5769 // Instances are configuration for individual instances. Instance
5870 // configuration takes precedence over global configuration.
5971 Instances []InstanceConnConfig
@@ -95,6 +107,28 @@ func (c *portConfig) nextPort() int {
95107 return p
96108}
97109
110+ var (
111+ // Instance URI is in the format:
112+ // '/projects/<PROJECT>/locations/<REGION>/clusters/<CLUSTER>/instances/<INSTANCE>'
113+ // Additionally, we have to support legacy "domain-scoped" projects (e.g. "google.com:PROJECT")
114+ instURIRegex = regexp .MustCompile ("projects/([^:]+(:[^:]+)?)/locations/([^:]+)/clusters/([^:]+)/instances/([^:]+)" )
115+ )
116+
117+ // UnixSocketDir returns a shorted instance connection name to prevent exceeding
118+ // the Unix socket length.
119+ func UnixSocketDir (dir , inst string ) (string , error ) {
120+ m := instURIRegex .FindSubmatch ([]byte (inst ))
121+ if m == nil {
122+ return "" , fmt .Errorf ("invalid instance name: %v" , inst )
123+ }
124+ project := string (m [1 ])
125+ region := string (m [3 ])
126+ cluster := string (m [4 ])
127+ name := string (m [5 ])
128+ shortName := strings .Join ([]string {project , region , cluster , name }, "." )
129+ return filepath .Join (dir , shortName ), nil
130+ }
131+
98132// Client represents the state of the current instantiation of the proxy.
99133type Client struct {
100134 cmd * cobra.Command
@@ -106,31 +140,79 @@ type Client struct {
106140
107141// NewClient completes the initial setup required to get the proxy to a "steady" state.
108142func NewClient (ctx context.Context , d alloydb.Dialer , cmd * cobra.Command , conf * Config ) (* Client , error ) {
109- var mnts []* socketMount
110143 pc := newPortConfig (conf .Port )
144+ var mnts []* socketMount
111145 for _ , inst := range conf .Instances {
112- m := & socketMount {inst : inst .Name }
113- a := conf .Addr
114- if inst .Addr != "" {
115- a = inst .Addr
116- }
117- var np int
118- switch {
119- case inst .Port != 0 :
120- np = inst .Port
121- default : // use next increment from conf.Port
122- np = pc .nextPort ()
146+ var (
147+ // network is one of "tcp" or "unix"
148+ network string
149+ // address is either a TCP host port, or a Unix socket
150+ address string
151+ )
152+ // IF
153+ // a global Unix socket directory is NOT set AND
154+ // an instance-level Unix socket is NOT set
155+ // (e.g., I didn't set a Unix socket globally or for this instance)
156+ // OR
157+ // an instance-level TCP address or port IS set
158+ // (e.g., I'm overriding any global settings to use TCP for this
159+ // instance)
160+ // use a TCP listener.
161+ // Otherwise, use a Unix socket.
162+ if (conf .UnixSocket == "" && inst .UnixSocket == "" ) ||
163+ (inst .Addr != "" || inst .Port != 0 ) {
164+ network = "tcp"
165+
166+ a := conf .Addr
167+ if inst .Addr != "" {
168+ a = inst .Addr
169+ }
170+
171+ var np int
172+ switch {
173+ case inst .Port != 0 :
174+ np = inst .Port
175+ case conf .Port != 0 :
176+ np = pc .nextPort ()
177+ default :
178+ np = pc .nextPort ()
179+ }
180+
181+ address = net .JoinHostPort (a , fmt .Sprint (np ))
182+ } else {
183+ network = "unix"
184+
185+ dir := conf .UnixSocket
186+ if dir == "" {
187+ dir = inst .UnixSocket
188+ }
189+ ud , err := UnixSocketDir (dir , inst .Name )
190+ if err != nil {
191+ return nil , err
192+ }
193+ // Create the parent directory that will hold the socket.
194+ if _ , err := os .Stat (ud ); err != nil {
195+ if err = os .Mkdir (ud , 0777 ); err != nil {
196+ return nil , err
197+ }
198+ }
199+ // use the Postgres-specific socket name
200+ address = filepath .Join (ud , ".s.PGSQL.5432" )
123201 }
124- addr , err := m .listen (ctx , "tcp" , net .JoinHostPort (a , fmt .Sprint (np )))
202+
203+ m := & socketMount {inst : inst .Name }
204+ addr , err := m .listen (ctx , network , address )
125205 if err != nil {
126206 for _ , m := range mnts {
127207 m .close ()
128208 }
129209 return nil , fmt .Errorf ("[%v] Unable to mount socket: %v" , inst .Name , err )
130210 }
211+
131212 cmd .Printf ("[%s] Listening on %s\n " , inst .Name , addr .String ())
132213 mnts = append (mnts , m )
133214 }
215+
134216 return & Client {mnts : mnts , cmd : cmd , dialer : d }, nil
135217}
136218
@@ -210,9 +292,9 @@ type socketMount struct {
210292}
211293
212294// listen causes a socketMount to create a Listener at the specified network address.
213- func (s * socketMount ) listen (ctx context.Context , network string , host string ) (net.Addr , error ) {
295+ func (s * socketMount ) listen (ctx context.Context , network string , address string ) (net.Addr , error ) {
214296 lc := net.ListenConfig {KeepAlive : 30 * time .Second }
215- l , err := lc .Listen (ctx , network , host )
297+ l , err := lc .Listen (ctx , network , address )
216298 if err != nil {
217299 return nil , err
218300 }
0 commit comments