Skip to content

Commit 94dde35

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 94dde35

4 files changed

Lines changed: 219 additions & 0 deletions

File tree

.github/workflows/conformance.yml

Lines changed: 83 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,84 @@ 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, Start, and Wait for Server
88+
id: server
89+
run: |
90+
# Use --no-console to prevent Gradle from hanging in a non-interactive CI shell.
91+
# The --no-daemon flag is also kept to ensure a fully detached process.
92+
nohup ./gradlew :sigstore-cli:runServer --no-daemon --no-console > server_stdout.log 2>&1 &
93+
SERVER_PID=$!
94+
echo "SERVER_PID=$SERVER_PID" >> $GITHUB_OUTPUT
95+
96+
# Give the server a moment to stabilize before checking if it died.
97+
echo "Waiting for server process to stabilize..."
98+
sleep 5
99+
100+
# Check if the server process died immediately after startup.
101+
if ! kill -0 $SERVER_PID 2>/dev/null; then
102+
echo "!! Server process died unexpectedly during startup."
103+
echo "--- Server STDOUT ---"
104+
cat server_stdout.log
105+
echo "--- Server STDERR ---"
106+
cat server_stderr.log
107+
exit 1
108+
fi
109+
110+
# If the process is alive, proceed with the readiness check.
111+
echo "Server process is running. Waiting for it to become ready..."
112+
for i in {1..15}; do
113+
if curl --output /dev/null --silent --head --fail "http://localhost:8080/cli"; then
114+
echo "Server is up!"
115+
exit 0
116+
fi
117+
echo "Waiting for server... attempt $i"
118+
sleep 1
119+
done
120+
121+
# If the loop finishes, the server is running but not responding.
122+
echo "!! Server started but did not become ready in time."
123+
echo "--- Server STDOUT ---"
124+
cat server_stdout.log
125+
echo "--- Server STDERR ---"
126+
cat server_stderr.log
127+
exit 1
128+
129+
- uses: sigstore/sigstore-conformance@fd90e6b0f3046f2276a6659481de6df495dea3b9 # v0.0.18
130+
with:
131+
entrypoint: ${{ github.workspace }}/sigstore-cli/sigstore-cli-server
132+
environment: ${{ matrix.sigstore-env }}
133+
xfail: "test_verify_dsse_bundle_with_trust_root"
134+
135+
- name: Stop server
136+
if: always()
137+
run: |
138+
echo "Stopping server with PID ${{ steps.server.outputs.SERVER_PID }}..."
139+
kill "${{ steps.server.outputs.SERVER_PID }}"

sigstore-cli/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ application {
2828
tasks.run.configure {
2929
workingDir = rootProject.projectDir
3030
}
31+
32+
tasks.register<JavaExec>("runServer") {
33+
group = "application"
34+
description = "Runs the CLI HTTP server"
35+
classpath = sourceSets.main.get().runtimeClasspath
36+
mainClass.set("dev.sigstore.cli.Server")
37+
}

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)