Skip to content

Commit 3681003

Browse files
committed
add config and setup for spanner-pg-starter
TAG=agy CONV=2dff9ccf-debd-4647-8a3e-3814e4c6013a
1 parent 6d109cc commit 3681003

9 files changed

Lines changed: 583 additions & 3 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ samples/**/composer.lock
4141
samples/**/.phpunit.result.cache
4242
src/test/**/vendor
4343
src/test/**/composer.lock
44+
45+
# PG Starter local build outputs and runtime caches
46+
/custom-jre/
47+
/install_path.txt
48+
/pgadapter.jar
49+
/pgadapter.jsa

pom.xml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,78 @@
327327
</plugins>
328328
</build>
329329
</profile>
330+
<profile>
331+
<id>shade-starter</id>
332+
<dependencies>
333+
<dependency>
334+
<groupId>org.slf4j</groupId>
335+
<artifactId>slf4j-nop</artifactId>
336+
<version>2.0.16</version>
337+
</dependency>
338+
</dependencies>
339+
<build>
340+
<plugins>
341+
<plugin>
342+
<groupId>org.apache.maven.plugins</groupId>
343+
<artifactId>maven-shade-plugin</artifactId>
344+
<executions>
345+
<execution>
346+
<goals>
347+
<goal>shade</goal>
348+
</goals>
349+
<configuration>
350+
<finalName>pgadapter</finalName>
351+
<filters>
352+
<filter>
353+
<artifact>*:*</artifact>
354+
<!-- We need to exclude the signatures for any signed jars otherwise
355+
we get an exception. -->
356+
<excludes>
357+
<exclude>META-INF/*.SF</exclude>
358+
<exclude>META-INF/*.DSA</exclude>
359+
<exclude>META-INF/*.RSA</exclude>
360+
</excludes>
361+
</filter>
362+
<filter>
363+
<artifact>com.google.cloud:google-cloud-spanner</artifact>
364+
<includes>
365+
<include>**</include>
366+
</includes>
367+
</filter>
368+
</filters>
369+
<createSourcesJar>true</createSourcesJar>
370+
<shadeSourcesContent>true</shadeSourcesContent>
371+
<shadedArtifactAttached>false</shadedArtifactAttached>
372+
<createDependencyReducedPom>false</createDependencyReducedPom>
373+
<minimizeJar>true</minimizeJar>
374+
<artifactSet>
375+
<includes>
376+
<include>*:*</include>
377+
</includes>
378+
<excludes>
379+
<exclude>java:*</exclude>
380+
<exclude>junit:*</exclude>
381+
</excludes>
382+
</artifactSet>
383+
<transformers>
384+
<transformer
385+
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
386+
<mainClass>com.google.cloud.spanner.pgadapter.SpannerPGStarter
387+
</mainClass>
388+
</transformer>
389+
<transformer
390+
implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
391+
<resource>META-INF/services</resource>
392+
<file>io.grpc.LoadBalancerProvider</file>
393+
</transformer>
394+
</transformers>
395+
</configuration>
396+
</execution>
397+
</executions>
398+
</plugin>
399+
</plugins>
400+
</build>
401+
</profile>
330402
<profile>
331403
<id>test-all</id>
332404
<properties>
@@ -398,6 +470,7 @@
398470
</plugins>
399471
</build>
400472
</profile>
473+
401474
<profile>
402475
<id>format</id>
403476
<activation>

src/main/java/com/google/cloud/spanner/pgadapter/Server.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public static void main(String[] args) {
109109
}
110110

111111
@VisibleForTesting
112-
public static void runCommand(
112+
public static int runCommand(
113113
ProxyServer proxyServer, @Nullable String database, String... command)
114114
throws IOException, InterruptedException {
115115
ProcessBuilder builder = new ProcessBuilder();
@@ -121,14 +121,15 @@ public static void runCommand(
121121
}
122122
builder.inheritIO();
123123
Process process = builder.start();
124-
process.waitFor();
124+
return process.waitFor();
125125
}
126126

127127
/**
128128
* Registers signal handlers for TERM, INT, and QUIT. This method uses reflection and fails
129129
* gracefully if signal handling is not available on this JVM.
130130
*/
131131
static void registerSignalHandlers() {
132+
boolean starterMode = Boolean.getBoolean("google.spanner.pgadapter.starter_mode");
132133
Class<?> signalClass = getSignalClass();
133134
Class<?> signalHandlerClass = getSignalHandlerClass();
134135
if (signalClass == null || signalHandlerClass == null) {
@@ -143,8 +144,18 @@ static void registerSignalHandlers() {
143144
Object intSignal = signalConstructor.newInstance("INT");
144145
Object quitSignal = signalConstructor.newInstance("QUIT");
145146

146-
Object termHandler = createSignalHandler(signalClass, signalHandlerClass, "handleTerm");
147147
Object intHandler = createSignalHandler(signalClass, signalHandlerClass, "handleInt");
148+
149+
if (starterMode) {
150+
try {
151+
handleMethod.invoke(null, intSignal, intHandler);
152+
} catch (Throwable throwable) {
153+
logger.log(Level.WARNING, "Failed to register signal handler for INT", throwable);
154+
}
155+
return;
156+
}
157+
158+
Object termHandler = createSignalHandler(signalClass, signalHandlerClass, "handleTerm");
148159
Object quitHandler = createSignalHandler(signalClass, signalHandlerClass, "handleQuit");
149160

150161
// Register the actual signal handlers. This will fail if the JVM has already registered a
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.cloud.spanner.pgadapter;
16+
17+
import com.google.cloud.spanner.pgadapter.logging.DefaultLogConfiguration;
18+
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
19+
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata.SslMode;
20+
import io.opentelemetry.api.OpenTelemetry;
21+
22+
/**
23+
* A simplified starter executable that sets up PGAdapter and runs a command (like psql) against it.
24+
* * Usage: spanner-pg-starter <command> [args...] Example: spanner-pg-starter psql -d
25+
* "projects/my-project/instances/my-inst/databases/my-db"
26+
*/
27+
public class SpannerPGStarter {
28+
29+
public static void main(String[] args) {
30+
System.setProperty("google.spanner.pgadapter.starter_mode", "true");
31+
// Scan for unsupported connection overrides
32+
for (String arg : args) {
33+
if (arg.equals("-h")
34+
|| arg.startsWith("--host")
35+
|| arg.equals("-p")
36+
|| arg.startsWith("--port")) {
37+
System.err.println("Error: Explicit host/port flags (-h/-p) are not supported.");
38+
System.err.println("spanner-pg-starter manages the connection routing dynamically.");
39+
System.exit(1);
40+
}
41+
}
42+
43+
if (args.length == 0) {
44+
System.out.println("Usage: spanner-pg-starter <command> [args...]");
45+
System.out.println(
46+
"Example: spanner-pg-starter psql -d \"projects/p/instances/i/databases/d\"");
47+
System.exit(1);
48+
}
49+
50+
try {
51+
DefaultLogConfiguration.disableLogging();
52+
OptionsMetadata.Builder builder =
53+
OptionsMetadata.newBuilder()
54+
.setPort(0)
55+
.setSslMode(SslMode.Disable)
56+
.disableUnixDomainSockets();
57+
58+
String projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
59+
if (projectId != null) {
60+
builder.setProject(projectId);
61+
}
62+
String instanceId = System.getenv("SPANNER_INSTANCE");
63+
if (instanceId != null) {
64+
builder.setInstance(instanceId);
65+
}
66+
67+
if (System.getenv("SPANNER_EMULATOR_HOST") != null) {
68+
builder.autoConfigureEmulator();
69+
}
70+
71+
OptionsMetadata options = builder.build();
72+
73+
OpenTelemetry openTelemetry = Server.setupOpenTelemetry(options);
74+
ProxyServer proxyServer = new ProxyServer(options, openTelemetry);
75+
proxyServer.startServer();
76+
77+
int exitCode = 0;
78+
try {
79+
exitCode = Server.runCommand(proxyServer, null, args);
80+
} finally {
81+
proxyServer.stopServer();
82+
}
83+
System.exit(exitCode);
84+
85+
} catch (Exception e) {
86+
e.printStackTrace();
87+
System.exit(1);
88+
}
89+
}
90+
}

starter/cloudbuild.yaml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Google Cloud Build - Spanner PG Starter Release Pipeline
2+
# Automates compiling the starter code, JRE stripping, packaging, and deploying to GCS.
3+
4+
substitutions:
5+
_VERSION: '0.0.0'
6+
_AR_LOCATION: 'us-central1'
7+
_AR_REPOSITORY: 'spanner-pg-starter'
8+
9+
steps:
10+
# Step 1: Compile and package the fat JAR using the shade-starter profile (JDK 25)
11+
- name: 'mirror.gcr.io/library/maven:3.9-eclipse-temurin-25'
12+
id: 'build-jar'
13+
entrypoint: 'mvn'
14+
args: ['-Pshade-starter', '-DskipTests', '-Dfmt.skip', 'clean', 'package']
15+
16+
# Step 2: Assemble the minimal platform-specific JREs via jlink cross-linking (JDK 25)
17+
- name: 'mirror.gcr.io/library/eclipse-temurin:25'
18+
id: 'link-jre'
19+
entrypoint: 'sh'
20+
args:
21+
- '-c'
22+
- |
23+
set -e
24+
apt-get update && apt-get install -y unzip curl
25+
26+
JDK_VER="jdk-25.0.3+9"
27+
28+
mkdir -p /tmp/downloads /tmp/jmods
29+
30+
# Download target JMOD packages directly from Adoptium APIs
31+
curl -fsSL -o /tmp/downloads/linux-x64.tar.gz "https://api.adoptium.net/v3/binary/latest/25/ga/linux/x64/jmods/hotspot/normal/eclipse"
32+
curl -fsSL -o /tmp/downloads/mac-aarch64.tar.gz "https://api.adoptium.net/v3/binary/latest/25/ga/mac/aarch64/jmods/hotspot/normal/eclipse"
33+
curl -fsSL -o /tmp/downloads/mac-x64.tar.gz "https://api.adoptium.net/v3/binary/latest/25/ga/mac/x64/jmods/hotspot/normal/eclipse"
34+
curl -fsSL -o /tmp/downloads/windows-x64.zip "https://api.adoptium.net/v3/binary/latest/25/ga/windows/x64/jmods/hotspot/normal/eclipse"
35+
36+
# Extract target JMOD directories
37+
tar -xzf /tmp/downloads/linux-x64.tar.gz -C /tmp/
38+
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/linux-x64
39+
40+
tar -xzf /tmp/downloads/mac-aarch64.tar.gz -C /tmp/
41+
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/mac-aarch64
42+
43+
tar -xzf /tmp/downloads/mac-x64.tar.gz -C /tmp/
44+
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/mac-x64
45+
46+
unzip -q /tmp/downloads/windows-x64.zip -d /tmp/
47+
mv "/tmp/$${JDK_VER}-jmods" /tmp/jmods/windows-x64
48+
49+
# Strip JRE to the bare minimum required for PGAdapter and Spanner Clients
50+
# Excludes unused desktop/GUI, RMI, scripting, and compiler modules
51+
MODULES="java.base,java.management,java.naming,java.security.jgss,java.sql,jdk.unsupported,jdk.crypto.cryptoki,jdk.crypto.ec"
52+
53+
# Execute cross-linking using the host JVM's native jlink command
54+
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
55+
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
56+
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
57+
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
58+
59+
# Step 3: Bundle assets into target platform packages
60+
- name: 'mirror.gcr.io/library/alpine:3.20'
61+
id: 'package-bundle'
62+
entrypoint: 'sh'
63+
args:
64+
- '-c'
65+
- |
66+
apk add --no-cache zip
67+
68+
mkdir -p target/release/versioned-package target/release/latest-assets
69+
70+
# Linux x64
71+
mkdir -p target/dist/linux-x64
72+
cp target/pgadapter.jar target/dist/linux-x64/
73+
cp -r target/custom-jres/linux-x64 target/dist/linux-x64/custom-jre
74+
cp starter/spanner-pg-starter target/dist/linux-x64/
75+
tar -czf target/release/versioned-package/spanner-pg-starter-linux-x64.tar.gz -C target/dist/linux-x64 spanner-pg-starter pgadapter.jar custom-jre/
76+
77+
# macOS aarch64
78+
mkdir -p target/dist/mac-aarch64
79+
cp target/pgadapter.jar target/dist/mac-aarch64/
80+
cp -r target/custom-jres/mac-aarch64 target/dist/mac-aarch64/custom-jre
81+
cp starter/spanner-pg-starter target/dist/mac-aarch64/
82+
tar -czf target/release/versioned-package/spanner-pg-starter-mac-aarch64.tar.gz -C target/dist/mac-aarch64 spanner-pg-starter pgadapter.jar custom-jre/
83+
84+
# macOS x64
85+
mkdir -p target/dist/mac-x64
86+
cp target/pgadapter.jar target/dist/mac-x64/
87+
cp -r target/custom-jres/mac-x64 target/dist/mac-x64/custom-jre
88+
cp starter/spanner-pg-starter target/dist/mac-x64/
89+
tar -czf target/release/versioned-package/spanner-pg-starter-mac-x64.tar.gz -C target/dist/mac-x64 spanner-pg-starter pgadapter.jar custom-jre/
90+
91+
# Windows x64
92+
mkdir -p target/dist/windows-x64
93+
cp target/pgadapter.jar target/dist/windows-x64/
94+
cp -r target/custom-jres/windows-x64 target/dist/windows-x64/custom-jre
95+
cp starter/spanner-pg-starter.cmd target/dist/windows-x64/
96+
cd target/dist/windows-x64
97+
zip -q -r ../../release/versioned-package/spanner-pg-starter-windows-x64.zip spanner-pg-starter.cmd pgadapter.jar custom-jre/
98+
cd ../../..
99+
100+
101+
options:
102+
requestedVerifyOption: VERIFIED
103+
logging: CLOUD_LOGGING_ONLY
104+
105+
artifacts:
106+
generic_artifacts:
107+
- registry_path: 'projects/${PROJECT_ID}/locations/${_AR_LOCATION}/repositories/${_AR_REPOSITORY}/packages/spanner-pg-starter/versions/${_VERSION}'
108+
folder: 'target/release/versioned-package'
109+

0 commit comments

Comments
 (0)