Skip to content

Commit e7642e7

Browse files
adityamparikhhossmanepugh
authored
feat: Add Docker support with Jib and GitHub Actions CI/CD (#6)
* Add Docker support with Jib and GitHub Actions CI/CD * Minimal .asf.yaml to get correct github notifications in place * Apply spotless plugin (#5) * Add Docker support with Jib and GitHub Actions CI/CD # Conflicts: # build.gradle.kts # gradle/libs.versions.toml * Update the repo name pattern. * Add Docker support with Jib and GitHub Actions CI/CD # Conflicts: # build.gradle.kts # gradle/libs.versions.toml * test: add Docker integration tests for MCP server under both STDIO and HTTP modes --------- Co-authored-by: Chris Hostetter <hossman@apache.org> Co-authored-by: Eric Pugh <epugh@opensourceconnections.com>
1 parent 0db92d1 commit e7642e7

13 files changed

Lines changed: 1390 additions & 94 deletions
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. 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+
# GitHub Actions Workflow: Build and Publish
17+
# ===========================================
18+
#
19+
# This workflow builds the Solr MCP Server project and publishes Docker images
20+
# to both GitHub Container Registry (GHCR) and Docker Hub.
21+
#
22+
# Workflow Triggers:
23+
# ------------------
24+
# 1. Push to 'main' branch - Builds, tests, and publishes Docker images
25+
# 2. Version tags (v*) - Builds and publishes release images with version tags
26+
# 3. Pull requests to 'main' - Only builds and tests (no publishing)
27+
# 4. Manual trigger via workflow_dispatch
28+
#
29+
# Jobs:
30+
# -----
31+
# 1. build: Compiles the JAR, runs tests, and uploads artifacts
32+
# 2. publish-docker: Publishes multi-platform Docker images using Jib
33+
#
34+
# Published Images:
35+
# ----------------
36+
# - GitHub Container Registry: ghcr.io/OWNER/solr-mcp-server:TAG
37+
# - Docker Hub: DOCKERHUB_USERNAME/solr-mcp-server:TAG
38+
#
39+
# Image Tagging Strategy:
40+
# ----------------------
41+
# - Main branch: VERSION-SHORT_SHA (e.g., 0.0.1-SNAPSHOT-a1b2c3d) + latest
42+
# - Version tags: VERSION (e.g., 1.0.0) + latest
43+
#
44+
# Required Secrets (for Docker Hub):
45+
# ----------------------------------
46+
# - DOCKERHUB_USERNAME: Your Docker Hub username
47+
# - DOCKERHUB_TOKEN: Docker Hub access token (https://hub.docker.com/settings/security)
48+
#
49+
# Note: GitHub Container Registry uses GITHUB_TOKEN automatically (no setup needed)
50+
51+
name: Build and Publish
52+
53+
on:
54+
push:
55+
branches:
56+
- main
57+
tags:
58+
- 'v*' # Trigger on version tags like v1.0.0, v2.1.3, etc.
59+
pull_request:
60+
branches:
61+
- main
62+
workflow_dispatch: # Allow manual workflow runs from GitHub UI
63+
64+
env:
65+
JAVA_VERSION: '25'
66+
JAVA_DISTRIBUTION: 'temurin'
67+
68+
jobs:
69+
# ============================================================================
70+
# Job 1: Build JAR
71+
# ============================================================================
72+
# This job compiles the project, runs tests, and generates build artifacts.
73+
# It runs on all triggers (push, PR, tags, manual).
74+
#
75+
# Outputs:
76+
# - Spring Boot JAR with all dependencies (fat JAR)
77+
# - Plain JAR without dependencies
78+
# - JUnit test results
79+
# - JaCoCo code coverage reports
80+
# ============================================================================
81+
build:
82+
name: Build JAR
83+
runs-on: ubuntu-latest
84+
85+
steps:
86+
# Checkout the repository code
87+
- name: Checkout code
88+
uses: actions/checkout@v4
89+
90+
# Set up Java Development Kit
91+
# Uses Temurin (Eclipse Adoptium) distribution of OpenJDK 25
92+
# Gradle cache is enabled to speed up subsequent builds
93+
- name: Set up JDK ${{ env.JAVA_VERSION }}
94+
uses: actions/setup-java@v4
95+
with:
96+
java-version: ${{ env.JAVA_VERSION }}
97+
distribution: ${{ env.JAVA_DISTRIBUTION }}
98+
cache: 'gradle'
99+
100+
# Make the Gradle wrapper executable
101+
# Required on Unix-based systems (Linux, macOS)
102+
- name: Grant execute permission for gradlew
103+
run: chmod +x gradlew
104+
105+
# Build the project with Gradle
106+
# This runs: compilation, tests, spotless formatting, error-prone checks,
107+
# JaCoCo coverage, and creates the JAR files
108+
- name: Build with Gradle
109+
run: ./gradlew build
110+
111+
# Upload the compiled JAR files as workflow artifacts
112+
# These can be downloaded from the GitHub Actions UI
113+
# Artifacts are retained for 7 days
114+
- name: Upload JAR artifact
115+
uses: actions/upload-artifact@v4
116+
with:
117+
name: solr-mcp-server-jar
118+
path: build/libs/solr-mcp-server-*.jar
119+
retention-days: 7
120+
121+
# Upload JUnit test results
122+
# if: always() ensures this runs even if the build fails
123+
# This allows viewing test results for failed builds
124+
- name: Upload test results
125+
if: always()
126+
uses: actions/upload-artifact@v4
127+
with:
128+
name: test-results
129+
path: build/test-results/
130+
retention-days: 7
131+
132+
# Upload JaCoCo code coverage report
133+
# if: always() ensures this runs even if tests fail
134+
# Coverage reports help identify untested code paths
135+
- name: Upload coverage report
136+
if: always()
137+
uses: actions/upload-artifact@v4
138+
with:
139+
name: coverage-report
140+
path: build/reports/jacoco/
141+
retention-days: 7
142+
143+
# ============================================================================
144+
# Job 2: Publish Docker Images
145+
# ============================================================================
146+
# This job builds multi-platform Docker images using Jib and publishes them
147+
# to GitHub Container Registry (GHCR) and Docker Hub.
148+
#
149+
# This job:
150+
# - Only runs after 'build' job succeeds (needs: build)
151+
# - Skips for pull requests (only runs on push to main and tags)
152+
# - Uses Jib to build without requiring Docker daemon
153+
# - Supports multi-platform: linux/amd64 and linux/arm64
154+
# - Publishes to both GHCR (always) and Docker Hub (if secrets configured)
155+
#
156+
# Security Note:
157+
# - Secrets are passed to Jib CLI arguments for authentication
158+
# - This is required for registry authentication and is handled securely
159+
# - GitHub Actions masks secret values in logs automatically
160+
# ============================================================================
161+
publish-docker:
162+
name: Publish Docker Images
163+
runs-on: ubuntu-latest
164+
needs: build # Wait for build job to complete successfully
165+
if: github.event_name != 'pull_request' # Skip for PRs
166+
167+
# Grant permissions for GHCR publishing
168+
# contents:read - Read repository contents
169+
# packages:write - Publish to GitHub Container Registry
170+
permissions:
171+
contents: read
172+
packages: write
173+
174+
steps:
175+
# Checkout the repository code
176+
- name: Checkout code
177+
uses: actions/checkout@v4
178+
179+
# Set up Java for running Jib
180+
# Jib doesn't require Docker but needs Java to run
181+
- name: Set up JDK ${{ env.JAVA_VERSION }}
182+
uses: actions/setup-java@v4
183+
with:
184+
java-version: ${{ env.JAVA_VERSION }}
185+
distribution: ${{ env.JAVA_DISTRIBUTION }}
186+
cache: 'gradle'
187+
188+
# Make Gradle wrapper executable
189+
- name: Grant execute permission for gradlew
190+
run: chmod +x gradlew
191+
192+
# Extract version and determine image tags
193+
# Outputs:
194+
# - version: Project version from build.gradle.kts
195+
# - tags: Comma-separated list of Docker tags to apply
196+
# - is_release: Whether this is a release build (from version tag)
197+
- name: Extract metadata
198+
id: meta
199+
run: |
200+
# Get version from build.gradle.kts
201+
VERSION=$(grep '^version = ' build.gradle.kts | sed 's/version = "\(.*\)"/\1/')
202+
echo "version=$VERSION" >> $GITHUB_OUTPUT
203+
204+
# Determine image tags based on trigger type
205+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
206+
# For version tags (e.g., v1.0.0), use semantic version
207+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
208+
echo "tags=$TAG_VERSION,latest" >> $GITHUB_OUTPUT
209+
echo "is_release=true" >> $GITHUB_OUTPUT
210+
else
211+
# For main branch, append short commit SHA for traceability
212+
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
213+
echo "tags=$VERSION-$SHORT_SHA,latest" >> $GITHUB_OUTPUT
214+
echo "is_release=false" >> $GITHUB_OUTPUT
215+
fi
216+
217+
# Authenticate to GitHub Container Registry
218+
# Uses built-in GITHUB_TOKEN (no configuration needed)
219+
- name: Log in to GitHub Container Registry
220+
uses: docker/login-action@v3
221+
with:
222+
registry: ghcr.io
223+
username: ${{ github.actor }}
224+
password: ${{ secrets.GITHUB_TOKEN }}
225+
226+
# Authenticate to Docker Hub
227+
# Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets
228+
# This step will fail silently if secrets are not configured
229+
# Create a Docker Hub access token, then add two GitHub Actions secrets named `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN`.
230+
#
231+
# Steps (web UI)
232+
# - Create Docker Hub token:
233+
# - Visit `https://hub.docker.com`
234+
# - Account → Settings → Security → New Access Token
235+
# - Copy the generated token (you can’t view it again).
236+
# - Add secrets to the repository:
237+
# - In GitHub, open the repo → `Settings` → `Secrets and variables` → `Actions` → `New repository secret`
238+
# - Add secret `DOCKERHUB_USERNAME` with your Docker Hub username.
239+
# - Add secret `DOCKERHUB_TOKEN` with the token from Docker Hub.
240+
#
241+
# Optional
242+
# - To make secrets available to multiple repos, add them at the organization level: Org → `Settings` → `Secrets and variables` → `Actions`.
243+
# - You can also add environment-level secrets if you use GitHub Environments.
244+
#
245+
# CLI example (GitHub CLI)
246+
# ```bash
247+
# gh secret set DOCKERHUB_USERNAME --body "your-docker-username"
248+
# gh secret set DOCKERHUB_TOKEN --body "your-docker-access-token"
249+
# ```
250+
#
251+
# Note: `GITHUB_TOKEN` is provided automatically for GHCR; do not store it manually.
252+
# - name: Log in to Docker Hub
253+
# uses: docker/login-action@v3
254+
# with:
255+
# username: ${{ secrets.DOCKERHUB_USERNAME }}
256+
# password: ${{ secrets.DOCKERHUB_TOKEN }}
257+
258+
# Convert repository owner to lowercase
259+
# Required because container registry names must be lowercase
260+
# Example: "Apache" -> "apache"
261+
- name: Determine repository owner (lowercase)
262+
id: repo
263+
run: |
264+
echo "owner_lc=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
265+
266+
# Build and publish images to GitHub Container Registry
267+
# Uses Jib Gradle plugin to build multi-platform images
268+
# Jib creates optimized, layered images without Docker daemon
269+
# Each tag is built and pushed separately
270+
- name: Build and publish to GitHub Container Registry
271+
run: |
272+
TAGS="${{ steps.meta.outputs.tags }}"
273+
IFS=',' read -ra TAG_ARRAY <<< "$TAGS"
274+
275+
# Build and push each tag to GHCR
276+
# Jib automatically handles multi-platform builds (amd64, arm64)
277+
for TAG in "${TAG_ARRAY[@]}"; do
278+
echo "Building and pushing ghcr.io/${{ steps.repo.outputs.owner_lc }}/solr-mcp-server:$TAG"
279+
./gradlew jib \
280+
-Djib.to.image=ghcr.io/${{ steps.repo.outputs.owner_lc }}/solr-mcp-server:$TAG \
281+
-Djib.to.auth.username=${{ github.actor }} \
282+
-Djib.to.auth.password=${{ secrets.GITHUB_TOKEN }}
283+
done
284+
285+
# Build and publish images to Docker Hub
286+
# Only runs if Docker Hub secrets are configured
287+
# Gracefully skips if secrets are not available
288+
- name: Build and publish to Docker Hub
289+
if: secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != ''
290+
run: |
291+
TAGS="${{ steps.meta.outputs.tags }}"
292+
IFS=',' read -ra TAG_ARRAY <<< "$TAGS"
293+
294+
# Build and push each tag to Docker Hub
295+
for TAG in "${TAG_ARRAY[@]}"; do
296+
echo "Building and pushing ${{ secrets.DOCKERHUB_USERNAME }}/solr-mcp-server:$TAG"
297+
./gradlew jib \
298+
-Djib.to.image=${{ secrets.DOCKERHUB_USERNAME }}/solr-mcp-server:$TAG \
299+
-Djib.to.auth.username=${{ secrets.DOCKERHUB_USERNAME }} \
300+
-Djib.to.auth.password=${{ secrets.DOCKERHUB_TOKEN }}
301+
done
302+
303+
# Create a summary of published images
304+
# Displayed in the GitHub Actions workflow summary page
305+
# Makes it easy to see which images were published and their tags
306+
- name: Summary
307+
run: |
308+
echo "### Docker Images Published :rocket:" >> $GITHUB_STEP_SUMMARY
309+
echo "" >> $GITHUB_STEP_SUMMARY
310+
echo "#### GitHub Container Registry" >> $GITHUB_STEP_SUMMARY
311+
TAGS="${{ steps.meta.outputs.tags }}"
312+
IFS=',' read -ra TAG_ARRAY <<< "$TAGS"
313+
for TAG in "${TAG_ARRAY[@]}"; do
314+
echo "- \`ghcr.io/${{ steps.repo.outputs.owner_lc }}/solr-mcp-server:$TAG\`" >> $GITHUB_STEP_SUMMARY
315+
done
316+
317+
# Only show Docker Hub section if secrets are configured
318+
if [[ "${{ secrets.DOCKERHUB_USERNAME }}" != "" ]]; then
319+
echo "" >> $GITHUB_STEP_SUMMARY
320+
echo "#### Docker Hub" >> $GITHUB_STEP_SUMMARY
321+
for TAG in "${TAG_ARRAY[@]}"; do
322+
echo "- \`${{ secrets.DOCKERHUB_USERNAME }}/solr-mcp-server:$TAG\`" >> $GITHUB_STEP_SUMMARY
323+
done
324+
fi

.github/workflows/build.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. 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+
116
name: SonarQube
217
on:
318
push:

.github/workflows/claude-code-review.yml

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)