Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
target
.cargo
*.log
*_out.html
62 changes: 62 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Docker build and smoke test

on:
push:
branches:
- main
- 'fix/docker-*'
paths:
Comment thread
NetLops marked this conversation as resolved.
Resolved
- Dockerfile
- .dockerignore
- docker-compose.yml
- Makefile
- Cargo.toml
- Cargo.lock
- src/**
- scripts/smoke-docker.py
- .github/workflows/docker.yml
pull_request:
paths:
- Dockerfile
- .dockerignore
- docker-compose.yml
- Makefile
- Cargo.toml
- Cargo.lock
- src/**
- scripts/smoke-docker.py
- .github/workflows/docker.yml
workflow_dispatch:

permissions:
contents: read

concurrency:
group: docker-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docker:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
PORT: '11380'
steps:
- uses: actions/checkout@v4

- name: Validate Compose configuration
run: docker compose config --quiet

- name: Build and start the service
run: make docker-compose-up

- name: Check HTTP startup and MCP protocol
run: python3 scripts/smoke-docker.py

- name: Show container logs
if: always()
run: docker compose logs --no-color

- name: Stop the service
if: always()
run: docker compose down
6 changes: 2 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM rust:bookworm as builder
FROM rust:bookworm AS builder

WORKDIR /usr/src/app

Expand All @@ -13,8 +13,6 @@ RUN apt-get update && apt-get install -y cmake clang
RUN mkdir -p .cargo && \
echo '[source.crates-io]\n\
replace-with = "rsproxy-sparse"\n\
[source.rsproxy]\n\
registry = "sparse+https://rsproxy.cn/index/"\n\
[source.rsproxy-sparse]\n\
registry = "sparse+https://rsproxy.cn/index/"\n\
[registries.rsproxy]\n\
Expand All @@ -25,7 +23,7 @@ git-fetch-with-cli = true' > .cargo/config.toml
COPY . .

# Build the application
RUN cargo build --release
RUN cargo build --release --locked

# Runtime stage
FROM debian:bookworm-slim
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
sosearch:
build:
Expand Down
50 changes: 50 additions & 0 deletions scripts/smoke-docker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Validate the built container without depending on public search engines."""

import json
import os
import subprocess
import time
import urllib.error
import urllib.request


port = os.environ.get("PORT", "11380")
url = f"http://127.0.0.1:{port}/search"
deadline = time.monotonic() + 30
while True:
try:
with urllib.request.urlopen(url, timeout=2):
raise AssertionError("HTTP must reject a missing query")
except urllib.error.HTTPError as error:
assert error.code == 400, f"Unexpected HTTP status: {error.code}"
break
except (urllib.error.URLError, TimeoutError):
if time.monotonic() >= deadline:
raise
time.sleep(1)
print("HTTP: service started and rejects missing query with 400")

requests = [
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}},
{"jsonrpc": "2.0", "id": 2, "method": "tools/list"},
{"jsonrpc": "2.0", "id": 3, "method": "ping"},
]
process = subprocess.run(
["docker", "compose", "run", "--rm", "--no-deps", "-T", "sosearch", "./SoSearch", "--mcp"],
input="".join(json.dumps(request) + "\n" for request in requests),
capture_output=True,
text=True,
timeout=30,
check=True,
)
responses = [json.loads(line) for line in process.stdout.splitlines()]
assert len(responses) == 3, process.stdout
assert [response["id"] for response in responses] == [1, 2, 3]
assert all(response["jsonrpc"] == "2.0" for response in responses)
assert responses[0]["result"]["serverInfo"]["name"] == "sosearch"
assert responses[0]["result"]["capabilities"] == {"tools": {}}
tool = responses[1]["result"]["tools"][0]
assert tool["name"] == "web_search"
assert tool["inputSchema"]["required"] == ["query"]
assert responses[2]["result"] == {}
print("MCP: initialize, tool discovery and ping passed")
Loading