Skip to content

gcp/cloudsql: add PSC and explicit IP type support via cloud-sql-go-connector#3678

Open
aplr wants to merge 6 commits intogoogle:masterfrom
aplr:master
Open

gcp/cloudsql: add PSC and explicit IP type support via cloud-sql-go-connector#3678
aplr wants to merge 6 commits intogoogle:masterfrom
aplr:master

Conversation

@aplr
Copy link
Copy Markdown

@aplr aplr commented Mar 23, 2026

Summary

Adds Private Service Connect (PSC) support and explicit IP type selection to gocloud.dev/gcp/cloudsql, gocloud.dev/postgres/gcppostgres, and gocloud.dev/mysql/gcpmysql by introducing the cloud.google.com/go/cloudsqlconn library alongside the existing cloudsql-proxy dependency.

Fixes #3670.


Changes

  • gcp/cloudsql: adds NewDialer / NewDialerWithIAM factory functions, a DialerSet Wire provider, and an IPType enum (IPTypeAuto, IPTypePublic, IPTypePrivate, IPTypePSC). Existing CertSourceSet / NewCertSource / NewCertSourceWithIAM are retained, marked deprecated.
  • URLOpener (both packages): gains a Dialer *cloudsqlconn.Dialer field and an IPType cloudsql.IPType field. Dialer takes precedence over CertSource when both are set. IP type can also be set per-URL via the ip_type query parameter (?ip_type=psc).
  • Default global openers (postgres.Open / mysql.Open via the registered scheme): now use Dialer internally. Functionally identical to the previous behavior — WithAutoIP() matches the legacy --auto-ip default.

Backwards Compatibility

This change is fully backwards compatible. The CertSource proxy.CertSource field is retained on both URLOpener structs (marked deprecated) and continues to work exactly as before. No existing code needs to change. cloudsql-proxy v1 remains a dependency for now.

The default behavior for users of postgres.Open / mysql.Open is unchanged: connections default to auto-IP selection (public if available, otherwise private).


Customization Parity & One Regression

cloud-sql-go-connector is at parity or better with cloudsql-proxy v1 on every dimension:

Feature v1 CertSource v2 Dialer
Custom HTTP client (SQL Admin API) certs.NewCertSourceOpts(httpClient, ...) WithHTTPClient(client)
Custom token source
IAM passwordless auth ✅ (+ separate API vs login token sources)
Custom dial function proxy.Client.ContextDialer ✅ global WithDialFunc + per-call WithOneOffDialFunc
IP type selection RemoteOpts.IPAddrTypeOpts ✅ per-call DialOption (cleaner)
PSC WithPSC()
Serverless/lazy refresh WithLazyRefresh()
DNS name resolution with failover WithDNSResolver()
Per-call dial options
Hard connection cap proxy.Client.MaxConnections ❌ only informational tracking

The one regression: MaxConnections — v1 enforced a hard connection limit via an atomic counter; v2 only tracks open connections informally without enforcing a cap.

One subtle behavioral change in the default global opener: previously the gcp.HTTPClient (using gcp.DefaultTransport()) was explicitly threaded into the cert source for SQL Admin API calls. The new Dialer-based opener derives an HTTP client from the token source internally. For standard GCP environments this is equivalent; users in environments with custom HTTP proxies or CA bundles who need that transport applied to SQL Admin API calls should construct the Dialer manually using cloudsqlconn.WithHTTPClient().


Migration Path to Dialer

To enable PSC or explicit IP type selection, replace CertSource with Dialer in your setup:

Direct construction:

// Before
opener := &gcppostgres.URLOpener{CertSource: cloudsql.NewCertSourceWithIAM(httpClient, ts)}

// After — supports PSC, IP type, lazy refresh, etc.
dialer, cleanup, err := cloudsql.NewDialerWithIAM(ctx, ts)
defer cleanup()
opener := &gcppostgres.URLOpener{Dialer: dialer}

With Wire:

// Before
wire.Build(gcpcloud.GCP, wire.Struct(new(gcpmysql.URLOpener), "CertSource"), ...)

// After
wire.Build(gcpcloud.GCP, cloudsql.DialerSet, wire.Struct(new(gcpmysql.URLOpener), "Dialer"), ...)

Via URL (no code change):

gcppostgres://user:pass@project/region/instance/dbname?ip_type=psc
gcpmysql://user:pass@project/region/instance/dbname?ip_type=private

Open Question: Drop cloudsql-proxy dependency?

The current implementation keeps cloudsql-proxy v1 to avoid a breaking change. The CertSource field and the legacy proxy code path remain fully functional.

An alternative would be to drop cloudsql-proxy entirely and make Dialer the only supported path. This would be a breaking change: anyone constructing URLOpener{CertSource: ...} or using cloudsql.NewCertSource / CertSourceSet would need to migrate to Dialer. The migration is mechanical (see above) and the functional behavior is identical by default.

Arguments for keeping it (current approach):

  • Zero migration cost for existing users
  • cloudsql-proxy v1 still works fine for users who don't need PSC

Arguments for dropping it:

  • Removes a deprecated, unmaintained dependency
  • Cleaner, smaller API surface
  • Avoids carrying dual code paths indefinitely

Happy to go either direction based on project preference.

@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 23, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@aplr aplr force-pushed the master branch 2 times, most recently from 0e7c9ec to a212d59 Compare March 24, 2026 00:37
…ompatible)

Add cloud.google.com/go/cloudsqlconn alongside the existing cloudsql-proxy
dependency. URLOpener now accepts both a Dialer (new, supports IP type
selection) and CertSource (existing, deprecated). Default URL-based
access uses Dialer, preserving auto-IP setting. CertSource remains
fully functional - no breaking changes.

Fixes google#3670.
@vangent
Copy link
Copy Markdown
Contributor

vangent commented Mar 25, 2026

Can you run gofmt -w -s on all the .go files you edited? And ./internal/testing/gomodcleanup.sh as well.

@vangent
Copy link
Copy Markdown
Contributor

vangent commented Mar 26, 2026

FAIL: dependencies changed; run: internal/testing/listdeps.sh > internal/testing/alldeps

@vangent
Copy link
Copy Markdown
Contributor

vangent commented Mar 26, 2026

Still failing:

Ensuring that there are no dependencies not listed in ./internal/testing/alldeps...
--- ./internal/testing/alldeps 2026-03-26 17:13:55.493758307 +0000
+++ - 2026-03-26 17:21:21.786030297 +0000
@@ -120,7 +120,6 @@
github.com/klauspost/compress
github.com/kylelemons/godebug
github.com/lib/pq
-github.com/mitchellh/go-homedir
github.com/mitchellh/mapstructure
github.com/montanaflynn/stats
github.com/munnerz/goautoneg
@@ -133,6 +132,7 @@
github.com/prometheus/client_model
github.com/prometheus/common
github.com/prometheus/otlptranslator
+github.com/prometheus/procfs
github.com/rabbitmq/amqp091-go
github.com/rcrowley/go-metrics
github.com/ryanuber/go-glob
FAIL: dependencies changed; run: internal/testing/listdeps.sh > internal/testing/alldeps
using the most recent go version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

postgres/gcppostgres: Add support for private service connect

2 participants