Visitar URL original
feat(tools): add cloud-sql-connect-gke for pg, mysql, mssql by mani-hari · Pull Request #4209 · googleapis/mcp-toolbox · GitHub
Skip to content

feat(tools): add cloud-sql-connect-gke for pg, mysql, mssql - #4209

Open
mani-hari wants to merge 2 commits into
googleapis:mainfrom
mani-hari:feat/cloud-sql-connect-gke
Open

mani-hari wants to merge 2 commits into
googleapis:mainfrom
mani-hari:feat/cloud-sql-connect-gke

Conversation

@mani-hari

Copy link
Copy Markdown
Contributor

Description

Adds cloud-sql-connect-gke, the second compute destination from the plan in #2345 (GCE shipped in #3740). Like the GCE tool, it is one tool for all engines: PostgreSQL, MySQL, and SQL Server are auto-detected via ParseDatabaseTypeStrict. It is read-only: it inspects the instance and the cluster, then returns a plan the agent can walk the user through.

GKE → Cloud SQL usually breaks because of cluster facts you can't see from inside a pod. The tool reads those facts from the GKE API and shapes everything it returns around them:

Cluster fact Effect on the output
Workload Identity off (Standard) Adds an "enable WI" step. The summary flags it as a cluster change.
Node pools not on GKE_METADATA Adds a per-pool node-pools update step. Pods would otherwise silently fall back to the node SA.
Routes-based cluster Private IP is treated as unreachable (PSA needs VPC-native).
Different VPC / Shared VPC host Networks are compared by project + name, so Shared VPC host projects match correctly.
Private nodes + public IP path Adds a Cloud NAT step in the network's (host) project.
PSC-only instance Adds the allowed-consumer patch (keeps the existing list), the PSC endpoint, and DNS steps.
Kubernetes ≥ 1.29 The proxy is emitted as a native sidecar (initContainers + restartPolicy: Always).
Cluster not RUNNING / no network path validation.valid=false. The summary starts with "Blocked:" and a remediation step.

Guiding the agent

GKE setup has a lot of steps, so the output is built to be easy for an agent to follow:

  • summary: a one-paragraph verdict to relay first. It is either the blocker, or the chosen method + network path + pending cluster changes. It also tells the agent to ask the user before mutating IAM, the cluster, or the instance.
  • setupSteps: numbered with no gaps, and limited to work that is still needed. Every command passes explicit --project / --location / -n flags. No step uses pipes, heredocs, or $(...), so each line can be shown and run as is.
  • Manifests: serviceAccountYaml, sidecarYaml, deploymentYaml, secretYaml. The Secret is created with kubectl create secret so passwords stay out of files.
  • troubleshooting: symptom / cause / fix for the failures users actually hit. Tips are filtered by method and sidecar type (for example startup races and Jobs that never finish only show up for classic sidecars).

Design decisions

  • Network path preference: private IP → public IP → PSC. Private IP is chosen only when the cluster is VPC-native and on the instance's network, because peering is not transitive. Public IP through the Auth Proxy is still mTLS-encrypted and IAM-authorized, and needs no authorized networks.
  • Auth Proxy sidecar is always primary. It is engine-agnostic and needs no code changes. The Connector library is offered as an alternative for PG/MySQL only, and direct private IP only when it is reachable.
  • No --auto-iam-authn by default: it breaks built-in database users. IAM database auth is reported as a validation check, and the troubleshooting tips explain when to add the flag.
  • cluster_project parameter: GKE often runs in a different project from the database. roles/cloudsql.client is granted in the instance project, and sqladmin is enabled in both.
  • cluster_location optional: if omitted, the tool does one locations/- list and matches the name client-side. On a miss, the error lists the clusters it found and any unreachable locations.
  • Input validation before any API call: cluster name, location, project, namespace, and KSA are checked against GCP / RFC 1123 rules, because they flow into shell commands, manifests, and IAM member strings.

Follows the review feedback from #3740

  • One tool for all engines; the sync.Once GKE client is initialized with context.Background(), and its token is scoped to the caller's OAuth token when the source sets useClientOAuth (ADC otherwise).
  • Shared logic lives in internal/util/cloudsqlconnect. The tool includes var _ tools.Tool = Tool{}, GetSourceName, ValidateSource, and ToConfig.
  • Floor-pinned snippet dependencies; docs YAML in kind: source / --- / kind: tool form; switch instead of if/else chains.

Scope: additive only

This PR adds new files only (21 files, +3452/−0). The single line outside the new tool is the standard registration in cmd/internal/imports.go.

  • The GCE tool and internal/util/cloudsqlconnect are unchanged. The tool reuses the shared helpers as they are (ValidateInstanceConnectionName, ExtractSQLInfo, ParseDatabaseTypeStrict, BuildConnectionStrings, GenerateCodeSnippet, and the shared result types).
  • GKE-only logic lives in the tool package as unexported helpers (gke.go, manifests.go, setup.go, validation.go, recommendations.go, inputvalidate.go). The file layout mirrors the shared package.
  • Extra output fields (summary, troubleshooting, serviceAccountYaml, deploymentYaml) come from tool-local types that embed cloudsqlconnect.ConnectResult and EnvironmentConfig. The JSON keys stay compatible with the GCE tool's output.
  • The pre-existing placeholder GKE helpers in the shared package (GenerateGKESetupSteps, GenerateGKESidecarYAML, ...) are not used by any tool and are left untouched. Consolidating them can be a separate follow-up if maintainers want it.

Testing

  • Unit tests:
    • cluster extraction
    • network matching (Shared VPC, project numbers)
    • native-sidecar version gating
    • path resolution
    • manifests (native vs classic, PSC / private flags, no --auto-iam-authn)
    • setup steps (WI off, node pools, namespace, PSC, NAT, blocker, sequential order, no shell operators)
    • troubleshooting
    • summary
    • input validators
    • YAML parsing
  • Integration tests for PG / MySQL / MSSQL (tests/cloudsql{pg,mysql,mssql}/*_connect_gke_test.go). They are skipped unless CLOUDSQL_TEST_GKE_CLUSTER_NAME is set; _LOCATION and _PROJECT are optional.

PR Checklist

🛠️ Part of #2345

Adds a read-only tool that plans the connection from a GKE workload to a Cloud SQL instance (engine auto-detected). It inspects Workload Identity, node pool metadata mode, VPC/Shared VPC, VPC-native networking, private nodes and PSC, then returns a summary, network path, Auth Proxy sidecar manifests, state-tailored setup steps and troubleshooting tips.

All GKE logic lives in the tool package and reuses the shared cloudsqlconnect helpers unchanged; the GCE tool and shared package are not modified.

Part of googleapis#2345
@mani-hari
mani-hari requested review from a team as code owners October 1, 2026 08:38

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the cloud-sql-connect-gke tool, which plans and configures connections from Google Kubernetes Engine (GKE) workloads to Cloud SQL instances (PostgreSQL, MySQL, or SQL Server). It includes comprehensive validation, connection recommendations, manifest generation, and step-by-step setup instructions. The review feedback highlights two key areas for improvement: updating the GKE client initialization to use a sync.RWMutex instead of sync.Once to prevent permanently caching transient initialization errors, and correcting the gcpLocationRe regular expression to support multi-segment GCP regions like us-gov-west1.

Comment on lines +36 to +40
var (
containerOnce sync.Once
containerService *container.Service
containerErr error
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using sync.Once to initialize the process-wide GKE client caches any transient initialization errors (such as temporary network issues or credential loading failures) permanently. In a long-running server, this means the GKE service will remain broken until the server is restarted. Use a sync.RWMutex to safely initialize the client without caching transient errors.

var (
	containerMu      sync.RWMutex
	containerService *container.Service
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecbda56. Replaced sync.Once with a sync.RWMutex and a double-checked init. Only a successfully built client is cached now, so a transient ADC or network failure goes back to that caller and the next request retries. It still uses context.Background() so a cancelled first request cannot poison the cache, and caller tokens still bypass it.

Comment on lines +50 to +63
func getContainerService(ctx context.Context, accessToken string) (*container.Service, error) {
if accessToken != "" {
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
svc, err := container.NewService(ctx, option.WithTokenSource(ts))
if err != nil {
return nil, fmt.Errorf("failed to build token-scoped GKE client: %w", err)
}
return svc, nil
}
containerOnce.Do(func() {
containerService, containerErr = container.NewService(context.Background(), option.WithScopes(container.CloudPlatformScope))
})
return containerService, containerErr
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Update getContainerService to use the sync.RWMutex double-checked locking pattern. This ensures that transient initialization errors are not cached permanently, allowing subsequent requests to retry GKE client creation.

func getContainerService(ctx context.Context, accessToken string) (*container.Service, error) {
	if accessToken != "" {
		ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
		svc, err := container.NewService(ctx, option.WithTokenSource(ts))
		if err != nil {
			return nil, fmt.Errorf("failed to build token-scoped GKE client: %w", err)
		}
		return svc, nil
	}

	containerMu.RLock()
	svc := containerService
	containerMu.RUnlock()
	if svc != nil {
		return svc, nil
	}

	containerMu.Lock()
	defer containerMu.Unlock()
	if containerService != nil {
		return containerService, nil
	}

	var err error
	containerService, err = container.NewService(context.Background(), option.WithScopes(container.CloudPlatformScope))
	if err != nil {
		return nil, fmt.Errorf("failed to build GKE client: %w", err)
	}
	return containerService, nil
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecbda56. getContainerService now follows the suggested pattern: RLock fast path, then Lock with a re-check, then build. On error it returns failed to build GKE client: %w without caching anything. The token-scoped path (useClientOAuth) is unchanged.

var (
projectIDRe = regexp.MustCompile(`^[a-z][-a-z0-9]{4,28}[a-z0-9]$`)
gkeClusterNameRe = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,38}[a-z0-9])?$`)
gcpLocationRe = regexp.MustCompile(`^[a-z]+-[a-z]+[0-9]+(-[a-z])?$`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current gcpLocationRe regex does not match valid GCP regions with multiple hyphens before the number, such as us-gov-west1 or us-gov-east1. Update the regex to support multi-segment geographies/regions.

Suggested change
gcpLocationRe = regexp.MustCompile(`^[a-z]+-[a-z]+[0-9]+(-[a-z])?$`)
gcpLocationRe = regexp.MustCompile("^[a-z]+(-[a-z]+)*[0-9]+(-[a-z])?$")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecbda56. gcpLocationRe is now ^[a-z]+(-[a-z]+)*[0-9]+(-[a-z])?$, so multi-segment regions like us-gov-west1 and their zones are accepted. Added test cases for those plus northamerica-northeast1, and negative cases (us-central, us-central1-ab, -us1) to make sure it is still strict.

… cloud-sql-connect-gke

- Replace sync.Once with an RWMutex double-checked init so a transient ADC/network failure is returned to that caller and retried on the next request instead of being cached for the life of the server.
- Broaden the cluster_location regex to accept multi-segment regions such as us-gov-west1 (and their zones); add test cases.
@mani-hari

Copy link
Copy Markdown
Contributor Author

Addressed the automated review in ecbda56:

  • GKE client init (high, x2): replaced sync.Once with an RWMutex double-checked init, so transient client-creation failures are retried instead of being cached for the life of the server.
  • cluster_location regex (medium): now accepts multi-segment regions such as us-gov-west1 and their zones, with new positive and negative test cases.

Unit tests, go vet and golangci-lint pass locally. Both changes are inside the new tool package; nothing outside it changed. The integration-test-pr and toolbox-evals-pr Cloud Build checks need a maintainer /gcbrun to run.

This branch has not been deployed

No deployments
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.

2 participants