Upgrade Spring Boot from 3.5.x to 4.0.3 #202
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Preview and Visual Diff | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| paths: | |
| - 'src/main/resources/templates/**' | |
| - 'src/main/resources/static/**' | |
| - 'src/main/resources/explanations/**' | |
| - 'src/main/java/**' | |
| permissions: | |
| contents: read | |
| packages: write | |
| pull-requests: write | |
| jobs: | |
| build-preview: | |
| runs-on: ubuntu-latest | |
| if: github.event.action != 'closed' | |
| outputs: | |
| image-tag: ${{ steps.meta.outputs.tags }} | |
| image-digest: ${{ steps.build.outputs.digest }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| - name: Set up JDK 25 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: "25" | |
| distribution: "temurin" | |
| cache: "maven" | |
| - name: Extract version from pom.xml | |
| id: extract-version | |
| run: | | |
| echo "Extracting version from pom.xml..." | |
| chmod +x ./mvnw | |
| VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) | |
| DOCKER_VERSION=${VERSION%-SNAPSHOT} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "docker_version=$DOCKER_VERSION" >> $GITHUB_OUTPUT | |
| echo "Detected version: $VERSION" | |
| echo "Docker version: $DOCKER_VERSION" | |
| - name: Build application | |
| run: ./mvnw --no-transfer-progress clean package -DskipTests | |
| - name: Verify JAR file was created | |
| run: | | |
| echo "Checking target directory..." | |
| ls -la target/ | |
| echo "Looking for JAR files..." | |
| find target/ -name "*.jar" -type f | |
| echo "Verifying specific JAR exists..." | |
| JAR_FILE="target/wrongsecrets-${{ steps.extract-version.outputs.version }}.jar" | |
| if [ -f "$JAR_FILE" ]; then | |
| echo "✅ JAR file found: $JAR_FILE" | |
| ls -la "$JAR_FILE" | |
| else | |
| echo "❌ Expected JAR file not found: $JAR_FILE" | |
| echo "Available JAR files:" | |
| find target/ -name "*.jar" -type f || echo "No JAR files found" | |
| exit 1 | |
| fi | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository }}/wrongsecrets-pr | |
| tags: | | |
| type=ref,event=pr,suffix=-{{sha}} | |
| type=ref,event=pr | |
| - name: Build and push Docker image | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64,linux/arm64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| argBasedVersion=${{ steps.extract-version.outputs.docker_version }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Verify Docker image was built | |
| run: | | |
| echo "Verifying Docker image was built successfully..." | |
| docker images | grep wrongsecrets-pr || echo "No wrongsecrets-pr images found" | |
| - name: Save Docker image as artifact | |
| run: | | |
| echo "Saving Docker image as tar artifact..." | |
| IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1) | |
| echo "Using image tag: $IMAGE_TAG" | |
| # Check if image is available locally first | |
| if docker images --format "table {{.Repository}}:{{.Tag}}" | grep -q "$IMAGE_TAG"; then | |
| echo "Image found locally, using local image" | |
| else | |
| echo "Image not found locally, attempting to pull..." | |
| if docker pull "$IMAGE_TAG"; then | |
| echo "Successfully pulled image" | |
| else | |
| echo "Failed to pull image, but this might be expected immediately after push" | |
| echo "Waiting 10 seconds and trying again..." | |
| sleep 10 | |
| docker pull "$IMAGE_TAG" || echo "Still failed to pull, continuing with local build" | |
| fi | |
| fi | |
| # Save the image as tar | |
| docker save "$IMAGE_TAG" -o wrongsecrets-preview.tar | |
| echo "Docker image saved to wrongsecrets-preview.tar" | |
| ls -lh wrongsecrets-preview.tar | |
| - name: Upload Docker image artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wrongsecrets-preview-pr-${{ github.event.number }} | |
| path: wrongsecrets-preview.tar | |
| retention-days: 30 | |
| # Comment out Render deployment for now | |
| # - name: Deploy to Render (Preview) | |
| # env: | |
| # RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} | |
| # run: | | |
| # # Create a temporary Render service for this PR | |
| # curl -X POST "https://api.render.com/v1/services" \ | |
| # -H "Authorization: Bearer $RENDER_API_KEY" \ | |
| # -H "Content-Type: application/json" \ | |
| # -d '{ | |
| # "type": "web_service", | |
| # "name": "wrongsecrets-pr-${{ github.event.number }}", | |
| # "runtime": "docker", | |
| # "dockerImage": { | |
| # "url": "${{ steps.meta.outputs.tags }}" | |
| # }, | |
| # "plan": "free", | |
| # "envVars": [ | |
| # {"key": "PORT", "value": "8080"} | |
| # ] | |
| # }' | |
| - name: Comment PR with build info | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const prNumber = context.issue.number; | |
| const imageTag = `${{ steps.meta.outputs.tags }}`.split('\n')[0]; | |
| const runId = context.runId; | |
| const comment = `🔨 **Preview Build Complete!** | |
| Your changes have been built and pushed to GitHub Container Registry. | |
| **🐳 Docker Image:** \`${imageTag}\` | |
| **📦 Download & Test Locally:** | |
| 1. [📁 Download Docker Image Artifact](https://github.com/${{ github.repository }}/actions/runs/${runId}) (look for \`wrongsecrets-preview-pr-${prNumber}\`) | |
| 2. Load and run the image: | |
| \`\`\`bash | |
| # Download the artifact, extract it, then: | |
| docker load < wrongsecrets-preview.tar | |
| docker run -p 8080:8080 wrongsecrets-preview | |
| \`\`\` | |
| **🚀 Alternative - Pull from Registry:** | |
| \`\`\`bash | |
| docker pull ${imageTag} | |
| docker run -p 8080:8080 ${imageTag} | |
| \`\`\` | |
| Then visit: http://localhost:8080 | |
| **📝 Changes in this PR:**`; | |
| // Get the list of changed files | |
| const { data: files } = await github.rest.pulls.listFiles({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber | |
| }); | |
| const relevantFiles = files.filter(file => | |
| file.filename.includes('templates/') || | |
| file.filename.includes('static/') || | |
| file.filename.includes('explanations/') || | |
| file.filename.includes('src/main/java/') | |
| ); | |
| let filesList = ''; | |
| if (relevantFiles.length > 0) { | |
| filesList = relevantFiles.slice(0, 10).map(file => `- \`${file.filename}\``).join('\n '); | |
| if (relevantFiles.length > 10) { | |
| filesList += `\n - ... and ${relevantFiles.length - 10} more files`; | |
| } | |
| } else { | |
| filesList = '- No relevant files changed'; | |
| } | |
| const finalComment = comment + '\n ' + filesList + ` | |
| Visual diff screenshots will be available shortly... | |
| --- | |
| <sub>Preview built by GitHub Actions</sub>`; | |
| github.rest.issues.createComment({ | |
| issue_number: prNumber, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: finalComment | |
| }); | |
| visual-diff: | |
| runs-on: ubuntu-latest | |
| needs: build-preview | |
| if: github.event.action != 'closed' | |
| steps: | |
| - name: Checkout PR code | |
| uses: actions/checkout@v5 | |
| with: | |
| path: pr-code | |
| - name: Checkout main branch | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: master | |
| path: main-code | |
| - name: Set up JDK 25 for PR build | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: "25" | |
| distribution: "temurin" | |
| cache: "maven" | |
| - name: Extract PR version | |
| id: extract-pr-version | |
| working-directory: pr-code | |
| run: | | |
| echo "Extracting PR version from pom.xml..." | |
| chmod +x ./mvnw | |
| VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) | |
| DOCKER_VERSION=${VERSION%-SNAPSHOT} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "docker_version=$DOCKER_VERSION" >> $GITHUB_OUTPUT | |
| echo "PR version: $VERSION" | |
| echo "PR Docker version: $DOCKER_VERSION" | |
| - name: Build PR version | |
| working-directory: pr-code | |
| run: | | |
| echo "Building PR version..." | |
| ./mvnw --no-transfer-progress clean package -DskipTests | |
| echo "PR JAR built successfully" | |
| docker build --build-arg argBasedVersion="${{ steps.extract-pr-version.outputs.docker_version }}" -t wrongsecrets-pr . | |
| echo "PR Docker image built successfully" | |
| - name: Set up JDK 25 for main | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: "25" | |
| distribution: "temurin" | |
| cache: "maven" | |
| - name: Extract main version | |
| id: extract-main-version | |
| working-directory: main-code | |
| run: | | |
| echo "Extracting main version from pom.xml..." | |
| chmod +x ./mvnw | |
| VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) | |
| DOCKER_VERSION=${VERSION%-SNAPSHOT} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "docker_version=$DOCKER_VERSION" >> $GITHUB_OUTPUT | |
| echo "Main version: $VERSION" | |
| echo "Main Docker version: $DOCKER_VERSION" | |
| - name: Build main version | |
| working-directory: main-code | |
| run: | | |
| echo "Building main version..." | |
| ./mvnw --no-transfer-progress clean package -DskipTests | |
| echo "Main JAR built successfully" | |
| docker build --build-arg argBasedVersion="${{ steps.extract-main-version.outputs.docker_version }}" -t wrongsecrets-main . | |
| echo "Main Docker image built successfully" | |
| # Alternative approach: Pull the PR image from registry | |
| # - name: Log in to GitHub Container Registry | |
| # uses: docker/login-action@v3 | |
| # with: | |
| # registry: ghcr.io | |
| # username: ${{ github.actor }} | |
| # password: ${{ secrets.GITHUB_TOKEN }} | |
| # - name: Pull PR image | |
| # run: | | |
| # docker pull ${{ needs.build-preview.outputs.image-tag }} | |
| # docker tag ${{ needs.build-preview.outputs.image-tag }} wrongsecrets-pr | |
| - name: Start both versions | |
| run: | | |
| docker run -d -p 8080:8080 --name pr-version wrongsecrets-pr | |
| docker run -d -p 8081:8080 --name main-version wrongsecrets-main | |
| # Wait for services to start | |
| echo "Waiting for services to start..." | |
| for i in {1..30}; do | |
| if curl -s http://localhost:8080 >/dev/null && curl -s http://localhost:8081 >/dev/null; then | |
| echo "Both services are ready!" | |
| break | |
| fi | |
| echo "Attempt $i/30: Services not ready yet..." | |
| sleep 2 | |
| done | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24' | |
| - name: Install Playwright | |
| run: | | |
| npm install playwright@latest | |
| npx playwright install --with-deps chromium | |
| - name: Take screenshots | |
| run: | | |
| mkdir -p screenshots | |
| # Verify services are still running | |
| echo "Verifying services are still running..." | |
| docker ps --filter "name=pr-version" --format "table {{.Names}}\t{{.Status}}" | |
| docker ps --filter "name=main-version" --format "table {{.Names}}\t{{.Status}}" | |
| # Test connectivity one more time | |
| echo "Testing connectivity..." | |
| curl -s -o /dev/null -w "%{http_code}" http://localhost:8080 || echo "PR version not responding" | |
| curl -s -o /dev/null -w "%{http_code}" http://localhost:8081 || echo "Main version not responding" | |
| node -e " | |
| const { chromium } = require('playwright'); | |
| (async () => { | |
| const browser = await chromium.launch({ headless: true }); | |
| const page = await browser.newPage(); | |
| await page.setViewportSize({ width: 1280, height: 1024 }); | |
| try { | |
| // PR version screenshots | |
| console.log('Taking PR screenshots...'); | |
| await page.goto('http://localhost:8080', { waitUntil: 'networkidle', timeout: 30000 }); | |
| await page.screenshot({ path: 'screenshots/pr-home.png', fullPage: true }); | |
| await page.goto('http://localhost:8080/about', { waitUntil: 'networkidle', timeout: 30000 }); | |
| await page.screenshot({ path: 'screenshots/pr-about.png', fullPage: true }); | |
| // Try to get a challenge page | |
| await page.goto('http://localhost:8080/challenge/1', { waitUntil: 'networkidle', timeout: 30000 }); | |
| await page.screenshot({ path: 'screenshots/pr-challenge.png', fullPage: true }); | |
| // Main version screenshots | |
| console.log('Taking main branch screenshots...'); | |
| await page.goto('http://localhost:8081', { waitUntil: 'networkidle', timeout: 30000 }); | |
| await page.screenshot({ path: 'screenshots/main-home.png', fullPage: true }); | |
| await page.goto('http://localhost:8081/about', { waitUntil: 'networkidle', timeout: 30000 }); | |
| await page.screenshot({ path: 'screenshots/main-about.png', fullPage: true }); | |
| await page.goto('http://localhost:8081/challenge/1', { waitUntil: 'networkidle', timeout: 30000 }); | |
| await page.screenshot({ path: 'screenshots/main-challenge.png', fullPage: true }); | |
| } catch (error) { | |
| console.error('Screenshot error:', error); | |
| process.exit(1); | |
| } finally { | |
| await browser.close(); | |
| } | |
| })(); | |
| " | |
| - name: Upload screenshots | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: visual-diff-pr-${{ github.event.number }} | |
| path: screenshots/ | |
| retention-days: 30 | |
| - name: Comment with visual diff | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const comment = `📸 **Visual Diff Ready!** | |
| Screenshots comparing your changes with the main branch are available: | |
| [📁 Download Visual Diff Artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| **🖼️ Included screenshots:** | |
| - \`pr-home.png\` vs \`main-home.png\` - Welcome page comparison | |
| - \`pr-about.png\` vs \`main-about.png\` - About page comparison | |
| - \`pr-challenge.png\` vs \`main-challenge.png\` - Challenge page comparison | |
| **🔍 How to review:** | |
| 1. Download the artifact zip file | |
| 2. Extract and compare the \`pr-*\` and \`main-*\` images side by side | |
| 3. Look for visual differences in layout, styling, and content | |
| **💡 Tip:** Use an image comparison tool or open both images in separate browser tabs to spot differences easily. | |
| --- | |
| <sub>Visual diff generated by GitHub Actions • PR #${{ github.event.number }}</sub>`; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| #only need to enable if you want to clean up resources on PR close: | |
| # cleanup-preview: | |
| # runs-on: ubuntu-latest | |
| # if: github.event.action == 'closed' | |
| # steps: | |
| # - name: Log in to GitHub Container Registry | |
| # uses: docker/login-action@v3 | |
| # with: | |
| # registry: ghcr.io | |
| # username: ${{ github.actor }} | |
| # password: ${{ secrets.GITHUB_TOKEN }} | |
| # - name: Delete PR container images | |
| # run: | | |
| # # Delete container images for this PR | |
| # PR_NUMBER=${{ github.event.number }} | |
| # REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') | |
| # # Get all tags for this PR and delete them | |
| # for tag in $(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ | |
| # "https://api.github.com/orgs/${{ github.repository_owner }}/packages/container/${REPO_LOWER}%2Fwrongsecrets-pr/versions" | \ | |
| # jq -r --arg pr "$PR_NUMBER" '.[] | select(.metadata.container.tags[]? | contains($pr)) | .id'); do | |
| # echo "Deleting container version: $tag" | |
| # curl -X DELETE \ | |
| # -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ | |
| # "https://api.github.com/orgs/${{ github.repository_owner }}/packages/container/${REPO_LOWER}%2Fwrongsecrets-pr/versions/$tag" | |
| # done | |
| # Comment out Render cleanup for now | |
| # - name: Cleanup Render service | |
| # env: | |
| # RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} | |
| # run: | | |
| # # Delete the temporary Render service | |
| # SERVICE_ID=$(curl -H "Authorization: Bearer $RENDER_API_KEY" \ | |
| # "https://api.render.com/v1/services" | \ | |
| # jq -r ".[] | select(.name==\"wrongsecrets-pr-${{ github.event.number }}\") | .id") | |
| # | |
| # if [ "$SERVICE_ID" != "null" ]; then | |
| # curl -X DELETE "https://api.render.com/v1/services/$SERVICE_ID" \ | |
| # -H "Authorization: Bearer $RENDER_API_KEY" | |
| # fi | |
| # - name: Comment PR closure | |
| # uses: actions/github-script@v7 | |
| # with: | |
| # script: | | |
| # const comment = `🧹 **Preview Cleanup Complete** | |
| # PR preview resources have been cleaned up: | |
| # - ✅ Container images deleted from GitHub Container Registry | |
| # - ✅ Artifacts will expire automatically in 30 days | |
| # Thanks for contributing to WrongSecrets! 🎉 | |
| # --- | |
| # <sub>Cleanup completed by GitHub Actions</sub>`; | |
| # github.rest.issues.createComment({ | |
| # issue_number: context.issue.number, | |
| # owner: context.repo.owner, | |
| # repo: context.repo.repo, | |
| # body: comment | |
| # }); |