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
0 commit comments