Skip to content

Commit dc14637

Browse files
committed
WIP: Use HTTP server for conformance testing
Signed-off-by: Aaron Lew <64337293+aaronlew02@users.noreply.github.com>
1 parent 47f52ac commit dc14637

4 files changed

Lines changed: 223 additions & 0 deletions

File tree

.github/workflows/conformance.yml

Lines changed: 76 additions & 0 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:
@@ -54,3 +56,77 @@ jobs:
5456
entrypoint: ${{ github.workspace }}/bin/sigstore-cli
5557
environment: ${{ matrix.sigstore-env }}
5658
xfail: "test_verify_dsse_bundle_with_trust_root"
59+
60+
conformance-server:
61+
strategy:
62+
max-parallel: 1
63+
matrix:
64+
java-version: [11, 17]
65+
sigstore-env: [production, staging]
66+
fail-fast: false
67+
68+
concurrency:
69+
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}-server-${{ matrix.java-version }}-${{ matrix.sigstore-env }}
70+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
71+
72+
runs-on: ubuntu-latest
73+
steps:
74+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
75+
with:
76+
persist-credentials: false
77+
78+
- name: Set up JDK ${{ matrix.java-version }}
79+
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
80+
with:
81+
java-version: ${{ matrix.java-version }}
82+
distribution: 'temurin'
83+
84+
- name: Setup Gradle
85+
uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0
86+
87+
- name: Build Server Executable JAR
88+
run: ./gradlew :sigstore-cli:shadowJar
89+
90+
- name: Start Server and Wait for It to Be Ready
91+
id: server
92+
run: |
93+
# Run the fat JAR directly with java -jar. This is a standard,
94+
# reliable Java process, completely detached from Gradle.
95+
nohup java -jar sigstore-cli/build/libs/sigstore-cli-server-*-all.jar > server.log 2>&1 &
96+
SERVER_PID=$!
97+
echo "SERVER_PID=$SERVER_PID" >> $GITHUB_OUTPUT
98+
99+
# Give the server a moment to start up
100+
sleep 5
101+
102+
# The rest of the readiness check and logging remains the same
103+
if ! kill -0 $SERVER_PID 2>/dev/null; then
104+
echo "!! Server process died unexpectedly."
105+
cat server.log
106+
exit 1
107+
fi
108+
109+
echo "Server process is running. Waiting for readiness..."
110+
for i in {1..15}; do
111+
if curl --output /dev/null --silent --head --fail "http://localhost:8080/cli"; then
112+
echo "Server is up!"
113+
exit 0
114+
fi
115+
sleep 1
116+
done
117+
118+
echo "!! Server did not become ready in time."
119+
cat server.log
120+
exit 1
121+
122+
- uses: sigstore/sigstore-conformance@fd90e6b0f3046f2276a6659481de6df495dea3b9 # v0.0.18
123+
with:
124+
entrypoint: ${{ github.workspace }}/sigstore-cli/sigstore-cli-server
125+
environment: ${{ matrix.sigstore-env }}
126+
xfail: "test_verify_dsse_bundle_with_trust_root"
127+
128+
- name: Stop server
129+
if: always()
130+
run: |
131+
echo "Stopping server with PID ${{ steps.server.outputs.SERVER_PID }}..."
132+
kill "${{ steps.server.outputs.SERVER_PID }}"

sigstore-cli/build.gradle.kts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id("build-logic.java")
33
id("application")
4+
id("com.github.johnrengelman.shadow") version "8.1.1"
45
}
56

67
repositories {
@@ -28,3 +29,20 @@ application {
2829
tasks.run.configure {
2930
workingDir = rootProject.projectDir
3031
}
32+
33+
tasks.shadowJar {
34+
archiveBaseName.set("sigstore-cli-server")
35+
archiveClassifier.set("all")
36+
manifest {
37+
attributes["Main-Class"] = "dev.sigstore.cli.Server"
38+
}
39+
}
40+
41+
/*
42+
tasks.register<JavaExec>("runServer") {
43+
group = "application"
44+
description = "Runs the CLI HTTP server"
45+
classpath = sourceSets.main.get().runtimeClasspath
46+
mainClass.set("dev.sigstore.cli.Server")
47+
}
48+
*/

sigstore-cli/sigstore-cli-server

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env bash
2+
set -o pipefail -o errexit -o nounset
3+
4+
PROCESSED_ARGS=()
5+
6+
ARGS_LIST=("$@")
7+
8+
i=0
9+
while [ $i -lt ${#ARGS_LIST[@]} ]; do
10+
ARG="${ARGS_LIST[$i]}"
11+
12+
# Check for flags that are followed by a file path argument.
13+
if [[ "$ARG" == "--bundle" || "$ARG" == "--output" ]]; then
14+
PROCESSED_ARGS+=("$ARG")
15+
((i++))
16+
path_arg="${ARGS_LIST[$i]}"
17+
18+
# Check if the path is already absolute.
19+
if [[ "$path_arg" =~ ^/ ]]; then
20+
# If the path starts with '/', it's absolute. Use it as-is.
21+
PROCESSED_ARGS+=("$path_arg")
22+
else
23+
# Otherwise, it's a relative path. Prepend the CWD.
24+
PROCESSED_ARGS+=("$(pwd)/$path_arg")
25+
fi
26+
# Handle artifact files (like 'a.txt') that are not preceded by a specific flag.
27+
elif [[ -f "$ARG" ]]; then
28+
if [[ "$ARG" =~ ^/ ]]; then
29+
PROCESSED_ARGS+=("$ARG")
30+
else
31+
PROCESSED_ARGS+=("$(pwd)/$ARG")
32+
fi
33+
# For all other flags and arguments, add them as-is.
34+
else
35+
PROCESSED_ARGS+=("$ARG")
36+
fi
37+
((i++))
38+
done
39+
40+
ARGS=$(printf '%s ' "${PROCESSED_ARGS[@]}")
41+
42+
RESPONSE=$(curl --silent -X POST --data-binary "$ARGS" http://localhost:8080/cli)
43+
44+
STDOUT=$(echo "$RESPONSE" | jq -r .stdout)
45+
STDERR=$(echo "$RESPONSE" | jq -r .stderr)
46+
EXIT_CODE=$(echo "$RESPONSE" | jq .exitCode)
47+
48+
echo -n "$STDOUT"
49+
>&2 echo -n "$STDERR"
50+
51+
exit "$EXIT_CODE"
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 com.sun.net.httpserver.HttpServer;
20+
import java.io.ByteArrayOutputStream;
21+
import java.io.IOException;
22+
import java.io.OutputStream;
23+
import java.io.PrintStream;
24+
import java.net.InetSocketAddress;
25+
import java.nio.charset.StandardCharsets;
26+
import java.util.Map;
27+
import picocli.CommandLine;
28+
29+
/** A simple HTTP server that executes sigstore-cli commands. */
30+
public class Server {
31+
32+
public static void main(String[] args) throws IOException {
33+
int port = 8080;
34+
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
35+
36+
Gson gson = new Gson();
37+
38+
server.createContext(
39+
"/cli",
40+
(exchange) -> {
41+
String command =
42+
new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
43+
String[] commandArgs = command.split("\\s+");
44+
45+
PrintStream originalOut = System.out;
46+
PrintStream originalErr = System.err;
47+
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
48+
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
49+
System.setOut(new PrintStream(outContent));
50+
System.setErr(new PrintStream(errContent));
51+
52+
int exitCode = 0;
53+
try {
54+
exitCode = new CommandLine(new Sigstore()).execute(commandArgs);
55+
} finally {
56+
System.setOut(originalOut);
57+
System.setErr(originalErr);
58+
}
59+
60+
Map<String, Object> responseMap =
61+
Map.of(
62+
"stdout", outContent.toString(),
63+
"stderr", errContent.toString(),
64+
"exitCode", exitCode);
65+
66+
String response = gson.toJson(responseMap);
67+
68+
exchange.sendResponseHeaders(200, response.length());
69+
try (OutputStream os = exchange.getResponseBody()) {
70+
os.write(response.getBytes());
71+
}
72+
});
73+
74+
server.setExecutor(null);
75+
System.out.println("Server started on port " + port);
76+
server.start();
77+
}
78+
}

0 commit comments

Comments
 (0)