Skip to content

Commit 331ac9f

Browse files
authored
Merge pull request #1038 from sigstore/conformance-server
Use HTTP server for conformance testing
2 parents e09a0f9 + 8bb6fc8 commit 331ac9f

File tree

4 files changed

+212
-6
lines changed

4 files changed

+212
-6
lines changed

.github/workflows/conformance.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
name: Conformance Tests
2+
permissions:
3+
contents: read
24

35
on:
46
push:
@@ -37,11 +39,14 @@ jobs:
3739
- name: Setup Gradle
3840
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
3941

40-
- name: Build sigstore-java cli
41-
run: ./gradlew :sigstore-cli:build
42-
43-
- name: Unpack sigstore-java distribution
44-
run: tar -xvf ${{ github.workspace }}/sigstore-cli/build/distributions/sigstore-cli-*.tar --strip-components 1
42+
- name: Build sigstore-java cli and server jar
43+
run: ./gradlew :sigstore-cli:serverShadowJar
44+
45+
- name: Start test server in background
46+
run: java -jar ${{ github.workspace }}/sigstore-cli/build/libs/sigstore-cli-server-all.jar &
47+
48+
- name: Wait for server to be ready
49+
run: curl --retry-connrefused --retry 10 --retry-delay 1 --fail http://localhost:8080/
4550

4651
- name: Set up JDK ${{ matrix.java-version }}
4752
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -51,6 +56,6 @@ jobs:
5156

5257
- uses: sigstore/sigstore-conformance@fd90e6b0f3046f2276a6659481de6df495dea3b9 # v0.0.18
5358
with:
54-
entrypoint: ${{ github.workspace }}/bin/sigstore-cli
59+
entrypoint: ${{ github.workspace }}/sigstore-cli/sigstore-cli-server
5560
environment: ${{ matrix.sigstore-env }}
5661
xfail: "test_verify_dsse_bundle_with_trust_root"

sigstore-cli/build.gradle.kts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2+
13
plugins {
24
id("build-logic.java")
35
id("application")
6+
id("com.gradleup.shadow") version "9.0.0-rc3"
47
}
58

69
repositories {
@@ -15,6 +18,11 @@ dependencies {
1518
implementation(platform("com.google.oauth-client:google-oauth-client-bom:1.39.0"))
1619
implementation("com.google.oauth-client:google-oauth-client")
1720

21+
implementation("org.eclipse.jetty:jetty-server:11.0.24")
22+
implementation("org.eclipse.jetty:jetty-servlet:11.0.24")
23+
24+
implementation("org.slf4j:slf4j-simple:2.0.17")
25+
1826
annotationProcessor("info.picocli:picocli-codegen:4.7.6")
1927
}
2028

@@ -25,6 +33,32 @@ tasks.compileJava {
2533
application {
2634
mainClass.set("dev.sigstore.cli.Sigstore")
2735
}
36+
37+
tasks.named("shadowDistTar") {
38+
enabled = false
39+
}
40+
41+
tasks.named("shadowDistZip") {
42+
enabled = false
43+
}
44+
2845
tasks.run.configure {
2946
workingDir = rootProject.projectDir
3047
}
48+
49+
tasks.register<ShadowJar>("serverShadowJar") {
50+
archiveBaseName.set("sigstore-cli-server")
51+
archiveClassifier.set("all")
52+
archiveVersion.set("")
53+
54+
mergeServiceFiles()
55+
56+
from(sourceSets.main.get().output)
57+
configurations = listOf(project.configurations.runtimeClasspath.get())
58+
59+
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
60+
61+
manifest {
62+
attributes("Main-Class" to "dev.sigstore.cli.ConformanceServer")
63+
}
64+
}

sigstore-cli/sigstore-cli-server

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
3+
set -o pipefail -o errexit -o nounset
4+
5+
CWD=$PWD
6+
7+
ARGS_JSON="["
8+
for arg in "$@"; do
9+
escaped_arg=$(echo -n "$arg" | jq -R -s '.')
10+
ARGS_JSON="$ARGS_JSON$escaped_arg,"
11+
done
12+
if [[ $ARGS_JSON == *, ]]; then
13+
ARGS_JSON="${ARGS_JSON%,}"
14+
fi
15+
ARGS_JSON="$ARGS_JSON]"
16+
17+
JSON_PAYLOAD=$(jq -nc --arg cwd "$CWD" --argjson args "$ARGS_JSON" '{"cwd": $cwd, "args": $args}')
18+
19+
RESPONSE=$(curl -s -X POST --header "Content-Type: application/json" --data-binary "$JSON_PAYLOAD" http://localhost:8080/execute)
20+
21+
STDOUT=$(echo "$RESPONSE" | jq -r .stdout)
22+
STDERR=$(echo "$RESPONSE" | jq -r .stderr)
23+
EXIT_CODE=$(echo "$RESPONSE" | jq .exitCode)
24+
25+
echo -n "$STDOUT"
26+
echo -n "$STDERR" >&2
27+
28+
exit "$EXIT_CODE"
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2025 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.cli;
17+
18+
import com.google.gson.Gson;
19+
import jakarta.servlet.ServletException;
20+
import jakarta.servlet.http.HttpServletRequest;
21+
import jakarta.servlet.http.HttpServletResponse;
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.IOException;
24+
import java.io.InputStream;
25+
import java.io.OutputStream;
26+
import java.io.PrintStream;
27+
import java.nio.charset.StandardCharsets;
28+
import java.nio.file.Path;
29+
import java.nio.file.Paths;
30+
import java.util.Map;
31+
import org.eclipse.jetty.server.Request;
32+
import org.eclipse.jetty.server.Server;
33+
import org.eclipse.jetty.server.handler.AbstractHandler;
34+
35+
public class ConformanceServer {
36+
37+
private static final Gson GSON = new Gson();
38+
39+
private static class ExecuteRequest {
40+
String cwd;
41+
String[] args;
42+
}
43+
44+
public static void main(String[] args) throws Exception {
45+
int port = 8080;
46+
Server server = new Server(port);
47+
server.setHandler(new ConformanceHandler());
48+
server.start();
49+
server.join();
50+
}
51+
52+
public static class ConformanceHandler extends AbstractHandler {
53+
@Override
54+
public void handle(
55+
String target,
56+
Request baseRequest,
57+
HttpServletRequest request,
58+
HttpServletResponse response)
59+
throws IOException, ServletException {
60+
if ("/".equals(target)) {
61+
handleHealthCheck(response);
62+
baseRequest.setHandled(true);
63+
} else if ("/execute".equals(target) && "POST".equals(request.getMethod())) {
64+
handleExecute(request, response);
65+
baseRequest.setHandled(true);
66+
}
67+
}
68+
}
69+
70+
private static void handleExecute(HttpServletRequest request, HttpServletResponse response)
71+
throws IOException {
72+
ExecuteRequest executeRequest;
73+
try (InputStream is = request.getInputStream()) {
74+
String requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);
75+
executeRequest = GSON.fromJson(requestBody, ExecuteRequest.class);
76+
}
77+
78+
// Tests should not be run in parallel, to ensure orderly input/output
79+
PrintStream originalOut = System.out;
80+
PrintStream originalErr = System.err;
81+
82+
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
83+
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
84+
85+
try (PrintStream outPs = new PrintStream(outContent, true, StandardCharsets.UTF_8);
86+
PrintStream errPs = new PrintStream(errContent, true, StandardCharsets.UTF_8)) {
87+
System.setOut(outPs);
88+
System.setErr(errPs);
89+
90+
Path cwd = Paths.get(executeRequest.cwd);
91+
java.util.List<String> resolvedArgs = new java.util.ArrayList<>();
92+
93+
for (int i = 0; i < executeRequest.args.length; i++) {
94+
String arg = executeRequest.args[i];
95+
96+
if (arg.equals("--bundle") && i + 1 < executeRequest.args.length) {
97+
resolvedArgs.add(arg);
98+
String filePath = executeRequest.args[++i];
99+
resolvedArgs.add(cwd.resolve(filePath).toAbsolutePath().toString());
100+
} else if (i == executeRequest.args.length - 1 && !arg.startsWith("-")) {
101+
if (arg.contains(":")) {
102+
resolvedArgs.add(arg);
103+
} else {
104+
resolvedArgs.add(cwd.resolve(arg).toAbsolutePath().toString());
105+
}
106+
} else {
107+
resolvedArgs.add(arg);
108+
}
109+
}
110+
111+
int exitCode =
112+
new picocli.CommandLine(new Sigstore()).execute(resolvedArgs.toArray(new String[0]));
113+
114+
Map<String, Object> responseMap =
115+
Map.of(
116+
"stdout", outContent.toString(StandardCharsets.UTF_8),
117+
"stderr", errContent.toString(StandardCharsets.UTF_8),
118+
"exitCode", exitCode);
119+
String jsonResponse = GSON.toJson(responseMap);
120+
121+
response.setStatus(HttpServletResponse.SC_OK);
122+
response.setContentType("application/json");
123+
byte[] responseBytes = jsonResponse.getBytes(StandardCharsets.UTF_8);
124+
response.setContentLength(responseBytes.length);
125+
126+
try (OutputStream os = response.getOutputStream()) {
127+
os.write(responseBytes);
128+
}
129+
} finally {
130+
System.setOut(originalOut);
131+
System.setErr(originalErr);
132+
}
133+
}
134+
135+
private static void handleHealthCheck(HttpServletResponse response) throws IOException {
136+
response.setStatus(HttpServletResponse.SC_OK);
137+
response.getWriter().println("OK");
138+
}
139+
}

0 commit comments

Comments
 (0)