Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ samples/**/composer.lock
samples/**/.phpunit.result.cache
src/test/**/vendor
src/test/**/composer.lock

# PG Starter local build outputs and runtime caches
/custom-jre/
/install_path.txt
/pgadapter.jar
/pgadapter.jsa
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@
</plugins>
</build>
</profile>

<profile>
<id>format</id>
<activation>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/google/cloud/spanner/pgadapter/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static void main(String[] args) {
}

@VisibleForTesting
public static void runCommand(
public static int runCommand(
ProxyServer proxyServer, @Nullable String database, String... command)
throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder();
Expand All @@ -121,7 +121,7 @@ public static void runCommand(
}
builder.inheritIO();
Process process = builder.start();
process.waitFor();
return process.waitFor();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.cloud.spanner.pgadapter;

import com.google.cloud.spanner.pgadapter.logging.DefaultLogConfiguration;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata.SslMode;
import io.opentelemetry.api.OpenTelemetry;

/**
* A simplified connector executable that sets up PGAdapter and runs a command (like psql) against
* it. * Usage: spgc <command> [args...] Example: spgc psql -d
* "projects/my-project/instances/my-inst/databases/my-db"
*/
public class SpannerPGConnector {

public static void main(String[] args) {
// Scan for unsupported connection overrides
for (String arg : args) {
if (arg.equals("-h")
|| arg.startsWith("--host")
|| arg.equals("-p")
|| arg.startsWith("--port")) {
System.err.println("Error: Explicit host/port flags (-h/-p) are not supported.");
System.err.println("spanner-pg-connector manages the connection routing dynamically.");
System.exit(1);
}
}

if (args.length == 0) {
System.out.println("Usage: spgc <command> [args...]");
System.out.println("Example: spgc psql -d \"projects/p/instances/i/databases/d\"");
System.exit(1);
}

try {
DefaultLogConfiguration.disableLogging();
OptionsMetadata.Builder builder =
OptionsMetadata.newBuilder()
.setPort(0)
.setSslMode(SslMode.Disable)
.disableUnixDomainSockets();

String projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
if (projectId != null) {
builder.setProject(projectId);
}
String instanceId = System.getenv("SPANNER_INSTANCE");
if (instanceId != null) {
builder.setInstance(instanceId);
}

if (System.getenv("SPANNER_EMULATOR_HOST") != null) {
builder.autoConfigureEmulator();
}

OptionsMetadata options = builder.build();

OpenTelemetry openTelemetry = Server.setupOpenTelemetry(options);
ProxyServer proxyServer = new ProxyServer(options, openTelemetry);
proxyServer.startServer();

int exitCode = 0;
try {
exitCode = Server.runCommand(proxyServer, null, args);
} finally {
proxyServer.stopServer();
}
System.exit(exitCode);

} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
117 changes: 117 additions & 0 deletions starter/cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Google Cloud Build - Spanner PG Starter Release Pipeline
# Automates compiling the starter code, JRE stripping, packaging, and deploying to GCS.

substitutions:
_VERSION: '0.0.0'
_AR_LOCATION: 'us-central1'
_AR_REPOSITORY: 'spanner-pg-connector'

steps:
# Step 1: Compile and package using assembly profile (JDK 25)
- name: 'mirror.gcr.io/library/maven:3.9-eclipse-temurin-25'
id: 'build-assembly'
entrypoint: 'mvn'
args: ['-Passembly', '-DskipTests', '-Dfmt.skip', 'clean', 'package']

# Step 2: Assemble the minimal platform-specific JREs via jlink cross-linking (JDK 25)
- name: 'mirror.gcr.io/library/eclipse-temurin:25'
id: 'link-jre'
entrypoint: 'sh'
args:
- '-c'
- |
set -e
apt-get update && apt-get install -y unzip curl

JDK_VER="jdk-25.0.3+9"

mkdir -p /tmp/downloads /tmp/jmods

# Download target JMOD packages directly from Adoptium APIs
curl -fsSL -o /tmp/downloads/linux-x64.tar.gz "https://api.adoptium.net/v3/binary/latest/25/ga/linux/x64/jmods/hotspot/normal/eclipse"
curl -fsSL -o /tmp/downloads/mac-aarch64.tar.gz "https://api.adoptium.net/v3/binary/latest/25/ga/mac/aarch64/jmods/hotspot/normal/eclipse"
curl -fsSL -o /tmp/downloads/mac-x64.tar.gz "https://api.adoptium.net/v3/binary/latest/25/ga/mac/x64/jmods/hotspot/normal/eclipse"
curl -fsSL -o /tmp/downloads/windows-x64.zip "https://api.adoptium.net/v3/binary/latest/25/ga/windows/x64/jmods/hotspot/normal/eclipse"

# Extract target JMOD directories
tar -xzf /tmp/downloads/linux-x64.tar.gz -C /tmp/
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/linux-x64

tar -xzf /tmp/downloads/mac-aarch64.tar.gz -C /tmp/
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/mac-aarch64

tar -xzf /tmp/downloads/mac-x64.tar.gz -C /tmp/
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/mac-x64

unzip -q /tmp/downloads/windows-x64.zip -d /tmp/
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/windows-x64

# Strip JRE to the bare minimum required for PGAdapter and Spanner Clients
# Excludes unused desktop/GUI, RMI, scripting, and compiler modules
MODULES="java.base,java.management,java.naming,java.security.jgss,java.sql,jdk.unsupported,jdk.crypto.cryptoki,jdk.crypto.ec"

# Execute cross-linking using the host JVM's native jlink command
jlink --module-path /tmp/jmods/linux-x64 --add-modules $${MODULES} --strip-debug --no-man-pages --no-header-files --compress=zip-9 --output target/custom-jres/linux-x64
jlink --module-path /tmp/jmods/mac-aarch64 --add-modules $${MODULES} --strip-debug --no-man-pages --no-header-files --compress=zip-9 --output target/custom-jres/mac-aarch64
jlink --module-path /tmp/jmods/mac-x64 --add-modules $${MODULES} --strip-debug --no-man-pages --no-header-files --compress=zip-9 --output target/custom-jres/mac-x64
jlink --module-path /tmp/jmods/windows-x64 --add-modules $${MODULES} --strip-debug --no-man-pages --no-header-files --compress=zip-9 --output target/custom-jres/windows-x64

# Step 3: Bundle assets into target platform packages
- name: 'mirror.gcr.io/library/alpine:3.20'
id: 'package-bundle'
entrypoint: 'sh'
args:
- '-c'
- |
apk add --no-cache zip

mkdir -p target/release/versioned-package target/release/latest-assets

# Linux x64
mkdir -p target/dist/linux-x64
cp target/pgadapter/pgadapter.jar target/dist/linux-x64/
cp -r target/pgadapter/lib target/dist/linux-x64/
cp -r target/custom-jres/linux-x64 target/dist/linux-x64/custom-jre
cp starter/spanner-pg-connector target/dist/linux-x64/
cp starter/spgc target/dist/linux-x64/
tar -czf target/release/versioned-package/spanner-pg-connector-linux-x64.tar.gz -C target/dist/linux-x64 spanner-pg-connector spgc pgadapter.jar lib/ custom-jre/

# macOS aarch64
mkdir -p target/dist/mac-aarch64
cp target/pgadapter/pgadapter.jar target/dist/mac-aarch64/
cp -r target/pgadapter/lib target/dist/mac-aarch64/
cp -r target/custom-jres/mac-aarch64 target/dist/mac-aarch64/custom-jre
cp starter/spanner-pg-connector target/dist/mac-aarch64/
cp starter/spgc target/dist/mac-aarch64/
tar -czf target/release/versioned-package/spanner-pg-connector-mac-aarch64.tar.gz -C target/dist/mac-aarch64 spanner-pg-connector spgc pgadapter.jar lib/ custom-jre/

# macOS x64
mkdir -p target/dist/mac-x64
cp target/pgadapter/pgadapter.jar target/dist/mac-x64/
cp -r target/pgadapter/lib target/dist/mac-x64/
cp -r target/custom-jres/mac-x64 target/dist/mac-x64/custom-jre
cp starter/spanner-pg-connector target/dist/mac-x64/
cp starter/spgc target/dist/mac-x64/
tar -czf target/release/versioned-package/spanner-pg-connector-mac-x64.tar.gz -C target/dist/mac-x64 spanner-pg-connector spgc pgadapter.jar lib/ custom-jre/

# Windows x64
mkdir -p target/dist/windows-x64
cp target/pgadapter/pgadapter.jar target/dist/windows-x64/
cp -r target/pgadapter/lib target/dist/windows-x64/
cp -r target/custom-jres/windows-x64 target/dist/windows-x64/custom-jre
cp starter/spanner-pg-connector.cmd target/dist/windows-x64/
cp starter/spgc.cmd target/dist/windows-x64/
cd target/dist/windows-x64
zip -q -r ../../release/versioned-package/spanner-pg-connector-windows-x64.zip spanner-pg-connector.cmd spgc.cmd pgadapter.jar lib/ custom-jre/
cd ../../..


options:
requestedVerifyOption: VERIFIED
logging: CLOUD_LOGGING_ONLY

artifacts:
generic_artifacts:
- registry_path: 'projects/${PROJECT_ID}/locations/${_AR_LOCATION}/repositories/${_AR_REPOSITORY}/packages/spanner-pg-connector/versions/${_VERSION}'
folder: 'target/release/versioned-package'

65 changes: 65 additions & 0 deletions starter/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# install.ps1
# PowerShell script to install Spanner PG Connector on Windows

$Project = "span-cloud-testing"
$Location = "us-central1"
$Repository = "spanner-pg-connector"
$Package = "spanner-pg-connector"
$Version = $env:VERSION
$InstallDir = Join-Path $HOME ".spanner-pg-connector"

# Resolve the latest version if not explicitly specified
if ([string]::IsNullOrEmpty($Version)) {
Write-Host "Querying Artifact Registry for the latest release version..."
if ($Token) {
$Uri = "https://artifactregistry.googleapis.com/v1/projects/$Project/locations/$Location/repositories/$Repository/packages/spanner-pg-connector/versions"
$Result = Invoke-RestMethod -Uri $Uri -Headers @{"Authorization" = "Bearer $Token"}
$Version = $Result.versions.name | ForEach-Object { Split-Path $_ -Leaf } | Where-Object { $_ -match '^\d+\.\d+\.\d+$' } | Sort-Object {[version]$_} | Select-Object -Last 1
} else {
$VersionsList = gcloud artifacts versions list --package=spanner-pg-connector --project=$Project --location=$Location --repository=$Repository --format="value(name)"
$Version = $VersionsList | Where-Object { $_ -match '^\d+\.\d+\.\d+$' } | Sort-Object {[version]$_} | Select-Object -Last 1
}

if ([string]::IsNullOrEmpty($Version)) {
Write-Error "Error: Could not resolve the latest version from Artifact Registry."
Exit 1
}
Write-Host "Resolved latest version: $Version" -ForegroundColor Cyan
}

Write-Host "Installing Spanner PG Connector..." -ForegroundColor Green

# 1. Clean previous installation
if (Test-Path $InstallDir) {
Remove-Item -Recurse -Force $InstallDir
}
New-Item -ItemType Directory -Path $InstallDir | Out-Null

# 2. Authenticate and download package
Write-Host "Downloading package from Artifact Registry..."
$Token = gcloud auth application-default print-access-token
$Headers = @{
"Authorization" = "Bearer $Token"
}
$DownloadUrl = "https://artifactregistry.googleapis.com/v1/projects/$Project/locations/$Location/repositories/$Repository/files/spanner-pg-connector:$Version:spanner-pg-connector-windows-x64.zip:download?alt=media"

$ZipPath = Join-Path $env:TEMP "spanner-pg-connector-windows-x64.zip"
Invoke-RestMethod -Uri $DownloadUrl -Headers $Headers -OutFile $ZipPath

# 3. Extract zip
Write-Host "Extracting files to $InstallDir..."
Expand-Archive -Path $ZipPath -DestinationPath $InstallDir -Force
Remove-Item $ZipPath

# 4. Add to user PATH environment variable if not already present
Write-Host "Configuring environment PATH..."
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($UserPath -split ";" -notcontains $InstallDir) {
[Environment]::SetEnvironmentVariable("Path", "$UserPath;$InstallDir", "User")
$env:PATH = "$env:PATH;$InstallDir"
Write-Host "Added $InstallDir to user PATH." -ForegroundColor Cyan
}
Write-Host "-----------------------------------------------------" -ForegroundColor Green
Write-Host "Installation complete!" -ForegroundColor Green
Write-Host "Please restart your PowerShell session or environment to begin using: spgc psql"
Write-Host "-----------------------------------------------------"
Loading
Loading