From 45cf02ad506739488715fb650cab8e39c3d41c24 Mon Sep 17 00:00:00 2001 From: robinfu Date: Thu, 5 Nov 2026 00:46:10 +0000 Subject: [PATCH] feat: add hidden --connect-tunnel flag for Connect Tunnel --- cmd/root.go | 30 +++++++++- cmd/root_test.go | 103 +++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- internal/proxy/proxy.go | 13 ++++- internal/proxy/proxy_test.go | 19 +++++++ 6 files changed, 164 insertions(+), 7 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 3b5b0e37e..dcf0c979e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -653,6 +653,11 @@ only applicable to Unix sockets)`) "Enable SQL Data to tunnel through the Cloud SQL Admin API without"+ " needing network access to your public or private IP", ) + localFlags.BoolVar(&c.conf.ConnectTunnel, "connect-tunnel", false, + "(*) Enable Connect Tunnel to tunnel through the Cloud SQL Admin API without"+ + " needing network access to your public or private IP", + ) + _ = localFlags.MarkHidden("connect-tunnel") return c } @@ -866,6 +871,9 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error { if userHasSetLocal(cmd, "private-ip") && userHasSetLocal(cmd, "auto-ip") { return newBadCommandError("cannot specify --private-ip and --auto-ip together") } + if userHasSetLocal(cmd, "connect-tunnel") && userHasSetLocal(cmd, "auto-ip") { + return newBadCommandError("cannot specify --connect-tunnel and --auto-ip together") + } // If more than one IP type is set, error. var ipTypes int @@ -878,8 +886,11 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error { if conf.SQLDataEnabled { ipTypes++ } + if conf.ConnectTunnel { + ipTypes++ + } if ipTypes > 1 { - return newBadCommandError("cannot specify --private-ip, --psc, and --sql-data flags at the same time") + return newBadCommandError("cannot specify --private-ip, --psc, --sql-data, and --connect-tunnel flags at the same time") } // If more than one auth method is set, error. @@ -1077,6 +1088,23 @@ and re-try with just --auto-iam-authn`) return newBadCommandError("cannot specify both private-ip and psc query params") } + ic.ConnectTunnel, err = parseBoolOpt(q, "connect-tunnel") + if err != nil { + return err + } + if ic.ConnectTunnel != nil && *ic.ConnectTunnel && conf.AutoIP { + return newBadCommandError("cannot use --auto-ip with connect-tunnel") + } + if ic.ConnectTunnel != nil && *ic.ConnectTunnel && conf.SQLDataEnabled { + return newBadCommandError("cannot use --sql-data with connect-tunnel") + } + if ic.SQLDataEnabled != nil && *ic.SQLDataEnabled && conf.ConnectTunnel { + return newBadCommandError("cannot use --connect-tunnel with sql-data") + } + if ic.ConnectTunnel != nil && (ic.PrivateIP != nil || ic.PSC != nil || ic.SQLDataEnabled != nil) { + return newBadCommandError("cannot specify connect-tunnel with private-ip, psc, or sql-data query params") + } + } ics = append(ics, ic) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 4c07de46c..e326d67b4 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -548,6 +548,40 @@ func TestNewCommandArguments(t *testing.T) { }}, }), }, + { + desc: "using the connect-tunnel flag", + args: []string{"--connect-tunnel", "proj:region:inst"}, + want: withDefaults(&proxy.Config{ + ConnectTunnel: true, + }), + }, + { + desc: "using the connect-tunnel and auto-iam-authn flags", + args: []string{"--connect-tunnel", "--auto-iam-authn", "proj:region:inst"}, + want: withDefaults(&proxy.Config{ + ConnectTunnel: true, + IAMAuthN: true, + }), + }, + { + desc: "using the connect-tunnel query param", + args: []string{"proj:region:inst?connect-tunnel=true"}, + want: withDefaults(&proxy.Config{ + Instances: []proxy.InstanceConnConfig{{ + ConnectTunnel: pointer(true), + }}, + }), + }, + { + desc: "using the connect-tunnel and auto-iam-authn query params", + args: []string{"proj:region:inst?connect-tunnel=true&auto-iam-authn=true"}, + want: withDefaults(&proxy.Config{ + Instances: []proxy.InstanceConnConfig{{ + ConnectTunnel: pointer(true), + IAMAuthN: pointer(true), + }}, + }), + }, { desc: "using the resource-exhausted-cooldown-delay flag", args: []string{"--resource-exhausted-cooldown-delay", "10s", "proj:region:inst"}, @@ -966,6 +1000,14 @@ func TestNewCommandWithEnvironmentConfig(t *testing.T) { SQLDataEndpoint: "https://test.googleapis.com", }), }, + { + desc: "using the connect-tunnel envvar", + envName: "CSQL_PROXY_CONNECT_TUNNEL", + envValue: "true", + want: withDefaults(&proxy.Config{ + ConnectTunnel: true, + }), + }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { @@ -1411,6 +1453,67 @@ func TestNewCommandWithErrors(t *testing.T) { "p:r:i", }, }, + { + desc: "using --connect-tunnel with --private-ip", + args: []string{ + "--connect-tunnel", "--private-ip", + "p:r:i", + }, + }, + { + desc: "using --connect-tunnel with --psc", + args: []string{ + "--connect-tunnel", "--psc", + "p:r:i", + }, + }, + { + desc: "using --connect-tunnel with --sql-data", + args: []string{ + "--connect-tunnel", "--sql-data", + "p:r:i", + }, + }, + { + desc: "using --connect-tunnel with --auto-ip", + args: []string{ + "--connect-tunnel", "--auto-ip", + "p:r:i", + }, + }, + { + desc: "using connect-tunnel query param with --auto-ip", + args: []string{ + "--auto-ip", + "p:r:i?connect-tunnel=true", + }, + }, + { + desc: "using connect-tunnel query param with --sql-data", + args: []string{ + "--sql-data", + "p:r:i?connect-tunnel=true", + }, + }, + { + desc: "using sql-data query param with --connect-tunnel", + args: []string{ + "--connect-tunnel", + "p:r:i?sql-data=true", + }, + }, + { + desc: "using connect-tunnel and private-ip query params", + args: []string{"p:r:i?connect-tunnel=true&private-ip=true"}, + }, + { + desc: "using connect-tunnel and psc query params", + args: []string{"p:r:i?connect-tunnel=true&psc=true"}, + }, + { + desc: "using connect-tunnel and sql-data query params", + args: []string{"p:r:i?connect-tunnel=true&sql-data=true"}, + }, { desc: "run-connection-test with fuse", args: []string{ diff --git a/go.mod b/go.mod index 5a52d14f4..3ceac00d7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/GoogleCloudPlatform/cloud-sql-proxy/v2 go 1.26.0 require ( - cloud.google.com/go/cloudsqlconn v1.26.0 + cloud.google.com/go/cloudsqlconn v1.26.1-0.20261111183309-d7d5ff5c4533 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.15-0.20230702191903-2de6d2748484 github.com/coreos/go-systemd/v22 v22.7.0 diff --git a/go.sum b/go.sum index ecb7e3d13..b2e886493 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/cloudsqlconn v1.26.0 h1:PAhid+sI4qighL3hZreR+VN4zAXQDRgwaKnoZJQQvXk= -cloud.google.com/go/cloudsqlconn v1.26.0/go.mod h1:QB+On8iYX22wh1pfAzHAwGvRn1zbQJoG1C9zPbXl9WU= +cloud.google.com/go/cloudsqlconn v1.26.1-0.20261111183309-d7d5ff5c4533 h1:HzoM7/uGpYOubgKJJ1AScTSddUTN2xTArtfKK1gUt10= +cloud.google.com/go/cloudsqlconn v1.26.1-0.20261111183309-d7d5ff5c4533/go.mod h1:ADS7Tej2MdeRCYRXLf/nsmAp8x1UxJLQL4lLnScMSiY= cloud.google.com/go/compute/metadata v0.10.0 h1:pyKMUQSwchgkIBBJGdILqQbs/BNJXqwSA7Ej6LAvvtY= cloud.google.com/go/compute/metadata v0.10.0/go.mod h1:rGFHRrIif570kSibjFTMbt6/4/tzgJWFGI/HVol4GIk= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index ce0df96aa..bfd2c3422 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -97,6 +97,8 @@ type InstanceConnConfig struct { UnixSocketPath string // SQLDataEnabled enables connections through the SqlDataService for this connection. SQLDataEnabled *bool + // ConnectTunnel enables connections through the SqlDataService without fallback for this connection. + ConnectTunnel *bool // IAMAuthN enables automatic IAM DB Authentication for the instance. // MySQL and Postgres only. If it is nil, the value was not specified. IAMAuthN *bool @@ -209,6 +211,8 @@ type Config struct { SQLDataEnabled bool // SQLDataEndpoint configures the endpoint of the SQL Data service. SQLDataEndpoint string + // ConnectTunnel enables connections through the SqlDataService without fallback for this connection. + ConnectTunnel bool // BuiltInMetrics enables or disables the Go Connector's built-in client // metrics (cloudsql.googleapis.com/client/connector/*), which are @@ -320,6 +324,8 @@ func dialOptions(c Config, i InstanceConnConfig) []cloudsqlconn.DialOption { // add the option. case i.PSC != nil && *i.PSC || i.PSC == nil && c.PSC: opts = append(opts, cloudsqlconn.WithPSC()) + case i.ConnectTunnel != nil && *i.ConnectTunnel || i.ConnectTunnel == nil && c.ConnectTunnel: + opts = append(opts, cloudsqlconn.WithConnectTunnel()) case c.AutoIP: opts = append(opts, cloudsqlconn.WithAutoIP()) default: @@ -638,10 +644,11 @@ func NewClient(ctx context.Context, d cloudsql.Dialer, l cloudsql.Logger, conf * return configureFUSE(c, conf) } - // unless the proxy is in SqlDataEnabled mode, initiate a refresh operation to warm the cache + // unless the proxy is in SqlDataEnabled or ConnectTunnel mode, initiate a refresh operation to warm the cache for _, inst := range conf.Instances { - // Skip instances with SqlDataEnabled - if conf.SQLDataEnabled || inst.SQLDataEnabled != nil && *inst.SQLDataEnabled { + // Skip instances with SqlDataEnabled or ConnectTunnel + if conf.SQLDataEnabled || inst.SQLDataEnabled != nil && *inst.SQLDataEnabled || + conf.ConnectTunnel || inst.ConnectTunnel != nil && *inst.ConnectTunnel { continue } go func(name string) { _, _ = d.EngineVersion(ctx, name) }(inst.Name) diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index f0dd14b19..776c8637f 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -735,6 +735,25 @@ func TestSQLDataWarmup(t *testing.T) { }, want: 0, }, + { + desc: "warmup skipped globally with ConnectTunnel", + in: &proxy.Config{ + ConnectTunnel: true, + Instances: []proxy.InstanceConnConfig{{Name: "proj:reg:inst", Port: 30107}}, + }, + want: 0, + }, + { + desc: "warmup skipped per-instance with ConnectTunnel", + in: &proxy.Config{ + Instances: []proxy.InstanceConnConfig{{ + Name: "proj:reg:inst", + Port: 30108, + ConnectTunnel: pointer(true), + }}, + }, + want: 0, + }, } for _, tc := range tcs {