diff --git a/.gitattributes b/.gitattributes index ae844ac..c2e3d1b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,20 @@ # Patches are applied on files extracted from a downloaded zip file. All these files use LF line endings. *.patch text eol=lf + +# Spring Initializr generates source files with LF line endings (Linux server). +# Explicit rules here prevent unexpected diffs when update-project.ps1 regenerates source on Windows, +# where core.autocrlf would otherwise convert committed files to CRLF on checkout. +**/source/**/*.java text eol=lf +**/source/**/*.gradle text eol=lf +**/source/**/*.properties text eol=lf +**/source/**/*.md text eol=lf +**/source/**/*.txt text eol=lf +**/source/**/*.xml text eol=lf +**/source/**/gradlew text eol=lf +**/source/**/gradlew.bat text eol=crlf +**/source/**/*.jar binary + +# Customizations are appended/overlaid onto the generated (LF) source by update-project.ps1, +# so keep them LF to avoid introducing CRLF when regenerating on Windows. +**/customizations/build.gradle.append text eol=lf +**/customizations/overlay/**/*.java text eol=lf diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml new file mode 100644 index 0000000..546efd4 --- /dev/null +++ b/.github/workflows/build-image.yaml @@ -0,0 +1,118 @@ +name: Build Image + +on: + workflow_call: + inputs: + image: + description: 'Image name (for example: config-server)' + required: true + type: string + description: + description: 'Human-readable image description for PR comments' + required: true + type: string + +jobs: + build-push: + name: Build and push image + runs-on: ubuntu-latest + env: + TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || 'edge' }} + REGISTRY: ${{ vars.DOCKER_REGISTRY }} + + steps: + - uses: actions/checkout@v4 + + - name: Read image config + id: config + shell: bash + run: | + echo "port=$(cat '${{ inputs.image }}/metadata/PORT' | tr -d '[:space:]')" >> $GITHUB_OUTPUT + echo "health_path=$(cat '${{ inputs.image }}/metadata/HEALTH_PATH' 2>/dev/null | tr -d '[:space:]' || echo '/actuator/health')" >> $GITHUB_OUTPUT + + - name: Set up JDK 25 + if: inputs.image != 'uaa-server' + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + cache: gradle + + - name: Build Image + run: ./build.ps1 -Name '${{ inputs.image }}' -Registry '${{ env.REGISTRY }}' -Tag '${{ env.TAG }}' + shell: pwsh + + # Confirm the freshly built image actually starts and reports healthy before we push it. + - name: Smoke test image + run: | + set -uo pipefail + image="${REGISTRY}/${{ inputs.image }}:${TAG}" + echo "Starting container from ${image}" + cid=$(docker run -d -p '${{ steps.config.outputs.port }}:${{ steps.config.outputs.port }}' "${image}") + url="http://localhost:${{ steps.config.outputs.port }}${{ steps.config.outputs.health_path }}" + echo "Polling ${url}" + ok=false + for i in $(seq 1 60); do + code=$(curl -s -o /dev/null -w '%{http_code}' "${url}" || true) + echo "attempt ${i}: HTTP ${code}" + if [ "${code}" = "200" ]; then ok=true; break; fi + sleep 3 + done + echo "----- container logs -----" + docker logs "${cid}" || true + docker rm -f "${cid}" >/dev/null 2>&1 || true + if [ "${ok}" != "true" ]; then + echo "Smoke test FAILED: ${url} did not return HTTP 200" + exit 1 + fi + echo "Smoke test passed" + shell: bash + + - name: Login to container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Push image + run: docker push ${{ env.REGISTRY }}/${{ inputs.image }}:${{ env.TAG }} + + - name: Post or update PR comment with image run instructions + uses: actions/github-script@v7 + if: ${{ github.event_name == 'pull_request' }} + with: + script: | + const marker = ''; + const body = `${marker} + To run the ${{ inputs.description }} image built for this pull request: + \`\`\`bash + docker run --rm -d --pull=always -p ${{ steps.config.outputs.port }}:${{ steps.config.outputs.port }} --name ${{ inputs.image }}-pr ${{ env.REGISTRY }}/${{ inputs.image }}:${{ env.TAG }} + \`\`\``; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + }); + + const existingComment = comments.find(comment => + comment.user.login === 'github-actions[bot]' && comment.body.startsWith(marker) + ); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body, + }); + } diff --git a/.github/workflows/build_config_server.yaml b/.github/workflows/build_config_server.yaml index 998dc2f..a79361c 100644 --- a/.github/workflows/build_config_server.yaml +++ b/.github/workflows/build_config_server.yaml @@ -4,16 +4,22 @@ on: pull_request: paths: - '.github/workflows/build_config_server.yaml' + - '.github/workflows/build-image.yaml' - 'config-server/metadata/*' - 'config-server/patches/*' + - 'config-server/customizations/**' + - 'config-server/source/**' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_config_server.yaml' + - '.github/workflows/build-image.yaml' - 'config-server/metadata/*' - 'config-server/patches/*' + - 'config-server/customizations/**' + - 'config-server/source/**' - 'build.ps1' concurrency: @@ -22,70 +28,12 @@ concurrency: permissions: contents: read - pull-requests: 'write' - -env: - IMAGE_NAME: config-server - REGISTRY: ${{ vars.DOCKER_REGISTRY }} + pull-requests: write jobs: - build-push: - name: Build and push image - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build Image - run: ./build.ps1 -Name '${{ env.IMAGE_NAME }}' -Registry '${{ env.REGISTRY }}' -Tag '${{ env.TAG }}' - shell: pwsh - env: - TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }} - - - name: Login to container registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Push image - run: docker push --all-tags ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Post or update PR comment with image run instructions - uses: actions/github-script@v7 - if: ${{ github.event_name == 'pull_request' }} - with: - script: | - const marker = ''; - const body = `${marker} - To run the Spring Cloud Config Server image built for this pull request: - \`\`\`bash - docker run --rm -d --pull=always -p 8888:8888 --name config-pr steeltoe.azurecr.io/config-server:pr-${{ github.event.number }} - \`\`\``; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - per_page: 100, - }); - - const existingComment = comments.find(comment => - comment.user.login === 'github-actions[bot]' && comment.body.startsWith(marker) - ); - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: body, - }); - } + build: + uses: ./.github/workflows/build-image.yaml + with: + image: config-server + description: Spring Cloud Config Server + secrets: inherit diff --git a/.github/workflows/build_eureka_server.yaml b/.github/workflows/build_eureka_server.yaml index 6ac942a..f606dd3 100644 --- a/.github/workflows/build_eureka_server.yaml +++ b/.github/workflows/build_eureka_server.yaml @@ -4,16 +4,22 @@ on: pull_request: paths: - '.github/workflows/build_eureka_server.yaml' + - '.github/workflows/build-image.yaml' - 'eureka-server/metadata/*' - 'eureka-server/patches/*' + - 'eureka-server/customizations/**' + - 'eureka-server/source/**' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_eureka_server.yaml' + - '.github/workflows/build-image.yaml' - 'eureka-server/metadata/*' - 'eureka-server/patches/*' + - 'eureka-server/customizations/**' + - 'eureka-server/source/**' - 'build.ps1' concurrency: @@ -22,70 +28,12 @@ concurrency: permissions: contents: read - pull-requests: 'write' - -env: - IMAGE_NAME: eureka-server - REGISTRY: ${{ vars.DOCKER_REGISTRY }} + pull-requests: write jobs: - build-push: - name: Build and push image - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build Image - run: ./build.ps1 -Name '${{ env.IMAGE_NAME }}' -Registry '${{ env.REGISTRY }}' -Tag '${{ env.TAG }}' - shell: pwsh - env: - TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }} - - - name: Login to container registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Push image - run: docker push --all-tags ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Post or update PR comment with image run instructions - uses: actions/github-script@v7 - if: ${{ github.event_name == 'pull_request' }} - with: - script: | - const marker = ''; - const body = `${marker} - To run the Eureka server image built for this pull request: - \`\`\`bash - docker run --rm -d --pull=always -p 8761:8761 --name eureka-pr steeltoe.azurecr.io/eureka-server:pr-${{ github.event.number }} - \`\`\``; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - per_page: 100, - }); - - const existingComment = comments.find(comment => - comment.user.login === 'github-actions[bot]' && comment.body.startsWith(marker) - ); - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: body, - }); - } + build: + uses: ./.github/workflows/build-image.yaml + with: + image: eureka-server + description: Eureka Server + secrets: inherit diff --git a/.github/workflows/build_springboot_admin_server.yaml b/.github/workflows/build_springboot_admin_server.yaml index d2ba5cf..2fc40e8 100644 --- a/.github/workflows/build_springboot_admin_server.yaml +++ b/.github/workflows/build_springboot_admin_server.yaml @@ -4,16 +4,22 @@ on: pull_request: paths: - '.github/workflows/build_springboot_admin_server.yaml' + - '.github/workflows/build-image.yaml' - 'spring-boot-admin/metadata/*' - 'spring-boot-admin/patches/*' + - 'spring-boot-admin/customizations/**' + - 'spring-boot-admin/source/**' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_springboot_admin_server.yaml' + - '.github/workflows/build-image.yaml' - 'spring-boot-admin/metadata/*' - 'spring-boot-admin/patches/*' + - 'spring-boot-admin/customizations/**' + - 'spring-boot-admin/source/**' - 'build.ps1' concurrency: @@ -22,70 +28,12 @@ concurrency: permissions: contents: read - pull-requests: 'write' - -env: - IMAGE_NAME: spring-boot-admin - REGISTRY: ${{ vars.DOCKER_REGISTRY }} + pull-requests: write jobs: - build-push: - name: Build and push image - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build Image - run: ./build.ps1 -Name '${{ env.IMAGE_NAME }}' -Registry '${{ env.REGISTRY }}' -Tag '${{ env.TAG }}' - shell: pwsh - env: - TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }} - - - name: Login to container registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Push image - run: docker push --all-tags ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Post or update PR comment with image run instructions - uses: actions/github-script@v7 - if: ${{ github.event_name == 'pull_request' }} - with: - script: | - const marker = ''; - const body = `${marker} - To run the Spring Boot Admin server image built for this pull request: - \`\`\`bash - docker run --rm -d --pull=always -p 9099:9099 --name sba-pr steeltoe.azurecr.io/spring-boot-admin:pr-${{ github.event.number }} - \`\`\``; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - per_page: 100, - }); - - const existingComment = comments.find(comment => - comment.user.login === 'github-actions[bot]' && comment.body.startsWith(marker) - ); - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: body, - }); - } + build: + uses: ./.github/workflows/build-image.yaml + with: + image: spring-boot-admin + description: Spring Boot Admin Server + secrets: inherit diff --git a/.github/workflows/build_uaa_server.yaml b/.github/workflows/build_uaa_server.yaml index eb7504a..b92a453 100644 --- a/.github/workflows/build_uaa_server.yaml +++ b/.github/workflows/build_uaa_server.yaml @@ -4,6 +4,7 @@ on: pull_request: paths: - '.github/workflows/build_uaa_server.yaml' + - '.github/workflows/build-image.yaml' - 'uaa-server/Dockerfile' - 'uaa-server/metadata/*' - 'uaa-server/*.yml' @@ -14,6 +15,7 @@ on: - main paths: - '.github/workflows/build_uaa_server.yaml' + - '.github/workflows/build-image.yaml' - 'uaa-server/Dockerfile' - 'uaa-server/metadata/*' - 'uaa-server/*.yml' @@ -26,70 +28,12 @@ concurrency: permissions: contents: read - pull-requests: 'write' - -env: - IMAGE_NAME: uaa-server - REGISTRY: ${{ vars.DOCKER_REGISTRY }} + pull-requests: write jobs: - build-push: - name: Build and push image - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build Image - run: ./build.ps1 -Name '${{ env.IMAGE_NAME }}' -Registry '${{ env.REGISTRY }}' -Tag '${{ env.TAG }}' - shell: pwsh - env: - TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }} - - - name: Login to container registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Push image - run: docker push --all-tags ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Post or update PR comment with image run instructions - uses: actions/github-script@v7 - if: ${{ github.event_name == 'pull_request' }} - with: - script: | - const marker = ''; - const body = `${marker} - To run the UAA server image built for this pull request: - \`\`\`bash - docker run --rm -d --pull=always -p 8080:8080 --name uaa-pr steeltoe.azurecr.io/uaa-server:pr-${{ github.event.number }} - \`\`\``; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - per_page: 100, - }); - - const existingComment = comments.find(comment => - comment.user.login === 'github-actions[bot]' && comment.body.startsWith(marker) - ); - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingComment.id, - body: body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: body, - }); - } + build: + uses: ./.github/workflows/build-image.yaml + with: + image: uaa-server + description: UAA Server + secrets: inherit diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..381dcd0 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,132 @@ +name: Release Images + +on: + release: + types: [published] + workflow_dispatch: + inputs: + image: + description: 'Image to release (all if empty)' + required: false + type: choice + options: + - all + - config-server + - eureka-server + - spring-boot-admin + - uaa-server + default: all + +permissions: + contents: read + packages: write + +env: + REGISTRY: ${{ vars.DOCKER_REGISTRY }} + +jobs: + release: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: [config-server, eureka-server, spring-boot-admin, uaa-server] + + steps: + - uses: actions/checkout@v4 + + - name: Filter Matrix + id: filter + run: | + if [[ "${{ inputs.image }}" != "" && "${{ inputs.image }}" != "all" && "${{ inputs.image }}" != "${{ matrix.image }}" ]]; then + echo "skip=true" >> $GITHUB_OUTPUT + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + shell: bash + + - name: Read image config + id: config + if: steps.filter.outputs.skip != 'true' + shell: bash + run: | + echo "port=$(cat '${{ matrix.image }}/metadata/PORT' | tr -d '[:space:]')" >> $GITHUB_OUTPUT + echo "health_path=$(cat '${{ matrix.image }}/metadata/HEALTH_PATH' 2>/dev/null | tr -d '[:space:]' || echo '/actuator/health')" >> $GITHUB_OUTPUT + + - name: Set up JDK 25 + if: steps.filter.outputs.skip != 'true' && matrix.image != 'uaa-server' + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + cache: gradle + + - name: Login to container registry + if: steps.filter.outputs.skip != 'true' + uses: docker/login-action@v3 + with: + registry: ${{ vars.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build + if: steps.filter.outputs.skip != 'true' + id: build + run: | + $Version = Get-Content "${{ matrix.image }}/metadata/IMAGE_VERSION" + $Revision = (Get-Content "${{ matrix.image }}/metadata/IMAGE_REVISION" -ErrorAction SilentlyContinue | ForEach-Object { $_.Trim() }) -join "" + + # When IMAGE_REVISION is set, the exact tag includes the suffix (for example: 4.3.1-2). + # The floating aliases (major.minor, major, latest) always track the highest revision. + $VersionTag = if ($Revision) { "$Version-$Revision" } else { $Version } + + ./build.ps1 -Name '${{ matrix.image }}' -Registry '${{ env.REGISTRY }}' -Tag $VersionTag + + echo "version_tag=$VersionTag" >> $env:GITHUB_OUTPUT + echo "version=$Version" >> $env:GITHUB_OUTPUT + shell: pwsh + + - name: Smoke test image + if: steps.filter.outputs.skip != 'true' + run: | + set -uo pipefail + image="${REGISTRY}/${{ matrix.image }}:${{ steps.build.outputs.version_tag }}" + echo "Starting container from ${image}" + cid=$(docker run -d -p '${{ steps.config.outputs.port }}:${{ steps.config.outputs.port }}' "${image}") + url="http://localhost:${{ steps.config.outputs.port }}${{ steps.config.outputs.health_path }}" + echo "Polling ${url}" + ok=false + for i in $(seq 1 60); do + code=$(curl -s -o /dev/null -w '%{http_code}' "${url}" || true) + echo "attempt ${i}: HTTP ${code}" + if [ "${code}" = "200" ]; then ok=true; break; fi + sleep 3 + done + echo "----- container logs -----" + docker logs "${cid}" || true + docker rm -f "${cid}" >/dev/null 2>&1 || true + if [ "${ok}" != "true" ]; then + echo "Smoke test FAILED: ${url} did not return HTTP 200" + exit 1 + fi + echo "Smoke test passed" + shell: bash + + - name: Push + if: steps.filter.outputs.skip != 'true' + run: | + $VersionTag = "${{ steps.build.outputs.version_tag }}" + $Version = "${{ steps.build.outputs.version }}" + $MajorMinor = $Version.Split('.')[0..1] -join '.' + $Major = $Version.Split('.')[0] + + $FullImage = "${{ env.REGISTRY }}/${{ matrix.image }}" + docker tag "${FullImage}:${VersionTag}" "${FullImage}:${MajorMinor}" + docker tag "${FullImage}:${VersionTag}" "${FullImage}:${Major}" + docker tag "${FullImage}:${VersionTag}" "${FullImage}:latest" + + docker push "${FullImage}:${VersionTag}" + docker push "${FullImage}:${MajorMinor}" + docker push "${FullImage}:${Major}" + docker push "${FullImage}:latest" + shell: pwsh diff --git a/.gitignore b/.gitignore index 10de664..922689d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,12 +6,19 @@ # Vi/Vim *.swp -# Gradle -/.gradle/ +# Gradle / IDE build output +.gradle/ build/ +bin/ # MacOS .DS_Store # Staging directory workspace/ + +# Gradle wrapper jar is downloaded at build time, not committed to source +**/gradle/wrapper/gradle-wrapper.jar + +# Spring Initializr boilerplate — no value in committed source +**/source/HELP.md diff --git a/AGENTS.md b/AGENTS.md index f5e7a80..7b9d23b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,59 +1,48 @@ -# Agent Instructions and Reminders +# Agent Instructions -This file contains important reminders and guidelines for AI agents working on this codebase. +See [README.md](README.md) for the full architecture, build flow, and maintenance workflows. This file covers agent-specific concerns only. -## Build Script +## Critical Rules -### Avoid `-DisableCache` Flag +- **Never edit `source/` directly.** It is fully overwritten by `update-project.ps1`. All substantive changes go in `patches/` or `customizations/`, then regenerate with `update-project.ps1`. +- **Never use `-DisableCache`** when running `build.ps1` from agentic contexts. It is effectively a no-op for Java images (source is committed) and risks triggering rate-limiting on start.spring.io for the UAA server. -**Do NOT use `-DisableCache`** when running `build.ps1` from agentic contexts. The `start.spring.io` service may block or rate-limit automated traffic, causing connection failures. - -Instead, to get a fresh build: +## Patch Files -1. Delete the expanded project folder (e.g., `workspace/springbootadmin/`) -2. Run `.\build.ps1 ` without the flag +Patches are applied by `update-project.ps1` using `patch -p1`, run from inside the extracted project directory. See [README.md § Architecture](README.md#architecture) for when to use a patch vs a customization. -### Testing Changes +### Patch Format Rules -Before submitting patch changes: +Agents frequently get hunk counts wrong. The format is: -1. Run a dry-run of each patch: `git apply --check ` -2. If dry-run succeeds, run the full build and verify Java compilation -3. Test the resulting Docker image with a real client app +``` +@@ -old_start,old_count +new_start,new_count @@ +``` -## Patch Files +- `old_count` = context lines + lines with a `-` prefix +- `new_count` = context lines + lines with a `+` prefix +- For new content in an (effectively) empty file: `@@ -0,0 +1,N @@` -The build script uses `git apply --unidiff-zero --recount --ignore-whitespace` to apply patches, which is more forgiving than the traditional `patch` command. +`patch -p1` does not auto-correct wrong counts — incorrect headers cause patch failures. -### Patch Format Rules +**Trailing newlines are required.** Patch files must end with a newline character. -1. **Hunk headers should be accurate**: The format is `@@ -old_start,old_count +new_start,new_count @@` - - `old_count` is the number of lines in the hunk from the old file (context lines plus lines with `-` prefix) - - `new_count` is the number of lines in the hunk in the new file (context lines plus lines with `+` prefix) - - For new file patches (`--- /dev/null`), `old_count` is 0 and `new_count` is the total number of lines in the new-file hunk - - Note: `--recount` will automatically correct line counts, but keeping them accurate is still good practice -2. **Trailing newlines are required**: Patch files must end with a newline character. -3. **Preserve exact whitespace**: Context lines must match the target file exactly, including trailing spaces and tabs. The `--ignore-whitespace` flag provides some tolerance but exact matches are preferred. -4. **New file patches**: Use `/dev/null` as the old file: +**Preserve exact whitespace.** Context lines must match the target file exactly. - ```diff - --- /dev/null - +++ ./path/to/NewFile.java 2026-01-27 00:00:00.000000000 +0000 - @@ -0,0 +1,N @@ - +line 1 - +line 2 - ... - ``` +**Path prefix with `-p1`:** Patches use paths like `configserver/src/...`; with `-p1` the applied path becomes `src/...`, matching the project layout. -### Example +### Example — Adding Lines -If a patch adds 1 line, the hunk header should reflect this: +If a patch adds 2 lines to a 3-line context block: ```diff --@@ -37,3 +37,10 @@ -+@@ -37,3 +37,11 @@ +-@@ -37,3 +37,3 @@ ++@@ -37,3 +37,5 @@ + context line 1 + context line 2 + context line 3 ++added line 1 ++added line 2 ``` -### Why This Matters - -While `git apply --recount` can fix minor line count issues, keeping patches accurate ensures reliable application and easier debugging. +`old_count = 3` (context), `new_count = 5` (3 context + 2 added). diff --git a/README.md b/README.md index e4a3e3f..44aaddf 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,80 @@ GitHub repo for server images to use for local development with SteeltoeOSS. +## Images + +| Name | Description | +| ---- | ----------- | +| [steeltoe.azurecr.io/config-server](config-server/) | Spring Cloud Config Server | +| [steeltoe.azurecr.io/eureka-server](eureka-server/) | Netflix Eureka Server | +| [steeltoe.azurecr.io/spring-boot-admin](spring-boot-admin/) | Spring Boot Admin | +| [steeltoe.azurecr.io/uaa-server](uaa-server/) | Cloud Foundry UAA Server | + +See [Common Tasks](https://github.com/SteeltoeOSS/Samples/blob/main/CommonTasks.md/) for instructions on how to run these images. + +--- + +## Architecture + +Excluding UAA, each server image (config-server, eureka-server, spring-boot-admin) has a ready-to-build Gradle project in its `source/` directory. The `source/` directory is regenerated from scratch by `update-project.ps1` whenever the Spring Boot version or dependencies need to change. + +```text +update-project.ps1 + │ 1. Download fresh project zip from start.spring.io + │ 2. Apply patches/ <-- modify Initializr-generated files + │ 3. Apply customizations/ <-- add content Initializr never generates + │ 4. Regenerate gradle.lockfile +/source/ <-- committed, ready-to-build + │ +build.ps1 --> bootBuildImage --> Docker image +``` + +### `patches/` vs `customizations/` + +Use **patches** when modifying content that Initializr generates (Java source files, `application.properties`). Patches are standard unified diff files applied with `patch -p1`. + +Use **customizations** for content that Initializr never generates: + +- **`build.gradle.append`** - appended to the generated `build.gradle`. Contains the image-build hardening: digest-pinned `builder`/`runImage`, a reproducible `createdDate`, `dependencyLocking`, and the `bootBuildImage` --> `test` dependency gate. An append is used rather than a patch because this block is entirely new content. A line-anchored patch at the end of a file that Initializr can restructure is fragile. +- **`overlay/`** - files copied verbatim over the generated project, mirroring the project layout. Contains hand-written tests and any other files Initializr would never produce (for example: `BasicOrNoAuthConfig.java` for config-server). + +Edit patches or customizations, **not `source/` directly**. Then run `update-project.ps1` to regenerate. + +### Reproducible Builds + +Images are built reproducibly: + +- Builder and run images are pinned by digest in `build.gradle.append` +- Dependency versions are locked in `gradle.lockfile` (regenerated by `update-project.ps1`) +- The image creation timestamp is derived from the git commit, not the wall clock + +### `gradle-wrapper.jar` + +The Gradle wrapper jar is **not committed** (`.gitignore`). `build.ps1` downloads it from GitHub on first use and validates its SHA-256 checksum against Gradle's official distribution on every build, regardless of whether the jar was just downloaded. This defends against a tampered or substituted jar from any source. + +--- + ## Building -### Pre-Requisites +### Prerequisites The following tools are required to build any image in this repository: 1. PowerShell or pwsh 1. Docker or Podman -#### Config Server, Eureka and Spring Boot Admin +Config Server, Eureka, and Spring Boot Admin additionally require: -The process for these images is to download starter projects from start.spring.io, apply patches to those files and produce images using [the Gradle Plugin](https://docs.spring.io/spring-boot/gradle-plugin/packaging-oci-image.html). -To build these images you must also have: - -1. Access to start.spring.io -1. `patch` available in the path or installed with Git for Windows 1. JDK 25 +1. Internet access (to download the Gradle distribution and Maven dependencies on first build) -If you do not already have a JDK installed, consider using [Scoop](https://scoop.sh/): +If you do not have a JDK installed, consider [Scoop](https://scoop.sh/): ```shell -# Permit executing remote-signed scripts Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -# Install Scoop Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression -# Add the Java bucket scoop bucket add java -# Install the JDK -scoop install java/openjdk25 +scoop install java/temurin25-jdk ``` ### Build a specific image @@ -39,34 +84,72 @@ scoop install java/openjdk25 ./build.ps1 config-server ``` -## Running +The build runs the project's tests first and fails if any test fails. -### List the created images +### List created images ```shell docker images ``` -See [Common Tasks](https://github.com/SteeltoeOSS/Samples/blob/main/CommonTasks.md/) for instructions on how to run the various docker images. +--- -## Images +## Updating Source -| Name | Description | -| ---- | ----------- | -| [steeltoe.azurecr.io/config-server](config-server/) | Spring Cloud Config Server | -| [steeltoe.azurecr.io/eureka-server](eureka-server/) | Netflix Eureka Server | -| [steeltoe.azurecr.io/spring-boot-admin](spring-boot-admin/) | Spring Boot Admin | -| [steeltoe.azurecr.io/uaa-server](uaa-server/) | Cloud Foundry UAA Server | +Run `update-project.ps1` to regenerate `source/` from start.spring.io and apply all patches and customizations: + +```shell +.\update-project.ps1 # update all Java images +.\update-project.ps1 -Names config-server # update one image +``` + +**Requires** JDK 25 and network access (resolves dependencies to regenerate `gradle.lockfile`). Also requires `patch` in your PATH (installed with Git for Windows). + +The script checks start.spring.io for a newer Spring Boot version in your current track (major.minor) and reports if a newer compatible version is available. It also resolves the current Spring Cloud or Spring Boot Admin artifact version from the Spring Cloud BOM POM and automatically updates `IMAGE_VERSION` when a new version is detected. + +### Workflow for a version bump + +1. Run `update-project.ps1` - it will auto-update `IMAGE_VERSION` in `metadata/` and regenerate `source/` +2. Review the changes in `source/` (especially the lockfile) +3. If patches no longer apply cleanly, update the relevant patch file +4. Run `./build.ps1 -Name ` to verify the Docker image build +5. Commit `metadata/`, `patches/`, `customizations/`, and `source/` + +### Workflow for a patch or customization change + +1. Edit the relevant file under `patches/` or `customizations/` +2. Run `update-project.ps1` to regenerate `source/` +3. Run `./build.ps1 -Name ` to verify the Docker image build +4. Commit everything + +--- + +## Releasing + +Releases are triggered by [publishing a GitHub release](https://github.com/SteeltoeOSS/Dockerfiles/releases/new). The release workflow builds and smoke-tests each image, then pushes the following tags: + +- `` - exact version (for example: `4.3.1`) +- `` - floating minor alias (for example: `4.3`) +- `` - floating major alias (for example: `4`) +- `latest` + +### `IMAGE_REVISION` - hotfix releases + +`IMAGE_REVISION` allows publishing a new image for the same package version without changing the version number (for example:, after a buildpack or infrastructure fix). The lifecycle: + +- **Normal release**: `IMAGE_REVISION` is empty --> published tag is `4.3.1`, floating aliases updated +- **Hotfix**: set `IMAGE_REVISION` to `2`, commit, trigger a release --> published tag is `4.3.1-2`, floating aliases updated +- **Subsequent hotfixes**: increment to `3`, `4`, etc. Do **not** clear `IMAGE_REVISION` after each release - the number stays set so the next hotfix knows where to start +- **Version bump**: clear `IMAGE_REVISION` (back to empty) when `IMAGE_VERSION` is bumped. `update-project.ps1` does this automatically when it detects and applies a new version. + +--- -## Debug Image Building +## Debugging -### Inspect Container Contents +### Inspect container contents -Via [StackOverflow](https://stackoverflow.com/questions/32353055/how-to-start-a-stopped-docker-container-with-a-different-command/39329138#39329138), here are the commands to list files in a stopped container. +Via [StackOverflow](https://stackoverflow.com/questions/32353055/how-to-start-a-stopped-docker-container-with-a-different-command/39329138#39329138): -1. Find the id of the stopped container - * `docker ps -a` -1. Commit the stopped container to a new image: test_image. - * `docker commit $CONTAINER_ID test_image` -1. Run the new image in a new container with a shell. - * `docker run -ti --entrypoint=sh test_image` +1. Find the id of the stopped container: `docker ps -a` +1. Commit it to a new image: `docker commit $CONTAINER_ID test_image` +1. Run with a shell: `docker run -ti --entrypoint=sh test_image` diff --git a/build.ps1 b/build.ps1 index 70676ac..050fa7f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -15,10 +15,9 @@ .DESCRIPTION Builds a specified Steeltoe Docker image. - By default, the image will be tagged using the name ':[[-]]' where: - image the specified Image name - version the value of 'IMAGE_VERSION' if specified in Dockerfile - rev the value of 'IMAGE_REVISION' if specified in Dockerfile + The image is tagged '/:' where is the value of -Tag if + specified; otherwise the image's metadata/IMAGE_VERSION when running in GitHub Actions; + otherwise 'dev' for local builds. .PARAMETER Help Print this message. @@ -27,7 +26,8 @@ List available images. .PARAMETER DisableCache - Disable caching of projects from start.spring.io. + Disable the Docker build layer cache. Only affects the UAA server; a no-op for the + Java images, which build from committed source. .PARAMETER Name Docker image name. @@ -36,7 +36,7 @@ Override the image tag. .PARAMETER Registry - Set the container registry. Defaults to dockerhub under steeltoeoss. + Set the container registry. Defaults to steeltoe.azurecr.io. #> # ----------------------------------------------------------------------------- @@ -109,26 +109,17 @@ try { throw "No metadata found for $Name" } - if (!$Tag) { - if ($env:GITHUB_ACTIONS -eq "true") { - $ImageNameWithTag = "$DockerOrg/${Name}:$Version" - $Revision = (Get-Content (Join-Path $ImageDirectory "metadata" "IMAGE_REVISION") -ErrorAction SilentlyContinue | ForEach-Object { $_.Trim() }) -join "" - if ($Revision -and $Revision -ne "") { - $ImageNameWithTag += "-$Revision" - } - $AdditionalTags = "$(Get-Content (Join-Path $ImageDirectory "metadata" "ADDITIONAL_TAGS") -ErrorAction SilentlyContinue | ForEach-Object { $_.replace("$Name","$DockerOrg/$Name") })" - } - else { - $ImageNameWithTag = "$DockerOrg/${Name}:dev" - $AdditionalTags = "" - } + if ($Tag) { + $ImageNameWithTag = "$DockerOrg/${Name}:$Tag" + } + elseif ($env:GITHUB_ACTIONS -eq "true") { + $ImageNameWithTag = "$DockerOrg/${Name}:$Version" } else { - $ImageNameWithTag = "$DockerOrg/${Name}:$Tag" - $AdditionalTags = "" + $ImageNameWithTag = "$DockerOrg/${Name}:dev" } - Write-Host "This image will be available as: $ImageNameWithTag $AdditionalTags" + Write-Host "This image will be available as: $ImageNameWithTag" if ($Name -eq "uaa-server") { $Dockerfile = Join-Path $ImageDirectory Dockerfile @@ -144,32 +135,15 @@ try { $NoCacheArg = "" } - $docker_command = "docker build $NoCacheArg -t $ImageNameWithTag $AdditionalTags $ImageDirectory --build-arg SERVER_VERSION=$Version" + $docker_command = "docker build $NoCacheArg -t $ImageNameWithTag $ImageDirectory --build-arg SERVER_VERSION=$Version" Write-Host $docker_command Invoke-Expression $docker_command } else { - if (!(Get-Command "git" -ErrorAction SilentlyContinue)) { - throw "'git' command not found" - } - - switch ($Name) { - "config-server" { - $appName = "ConfigServer" - $dependencies = "cloud-config-server,actuator,cloud-eureka,security" - } - "eureka-server" { - $appName = "EurekaServer" - $dependencies = "cloud-eureka-server,actuator" - } - "spring-boot-admin" { - $appName = "SpringBootAdmin" - $dependencies = "codecentric-spring-boot-admin-server" - } - Default { - Write-Host "$Name is not currently supported by this script" - exit 2 - } + $supportedImages = @("config-server", "eureka-server", "spring-boot-admin") + if ($Name -notin $supportedImages) { + Write-Host "$Name is not currently supported by this script" + exit 2 } $workPath = "workspace" @@ -179,13 +153,10 @@ try { Push-Location $workPath try { $serverName = $Name -replace '-', '' - $JVM = "25" - $bootVersion = Get-Content (Join-path $ImageDirectory "metadata" "SPRING_BOOT_VERSION") - $serverVersion = Get-Content (Join-Path $ImageDirectory "metadata" "IMAGE_VERSION") - $artifactName = "$serverName$serverVersion-boot$bootVersion-jvm$JVM.zip" + $Version = Get-Content (Join-Path $ImageDirectory "metadata" "IMAGE_VERSION") - Write-Host "Building server: $Name@$serverVersion on Spring Boot $bootVersion" - Write-Host "Source files: $ImageDirectory" + Write-Host "Building server: $Name@$Version" + Write-Host "Source files: $ImageDirectory/source" Write-Host "Working directory: $PWD" # Ensure clean workspace @@ -194,53 +165,66 @@ try { throw "Failed to remove existing workspace $serverName" } - if ($DisableCache -And (Test-Path "$artifactName")) { - Write-Host "Removing previously downloaded $artifactName" - Remove-Item -Force "$artifactName" + # Copy source from committed directory + $sourceDir = Join-Path $ImageDirectory "source" + if (!(Test-Path $sourceDir)) { + throw "Source directory not found at $sourceDir. Run update-project.ps1 first." } - # Scaffold project on start.spring.io - if (!(Test-Path "$artifactName")) { - Write-Host "Using start.spring.io to create project with dependencies: $dependencies" - Invoke-WebRequest ` - -Uri "https://start.spring.io/starter.zip" ` - -Method Post ` - -Body @{ - type = "gradle-project" - bootVersion = $bootVersion - javaVersion = $JVM - groupId = "io.steeltoe.docker" - artifactId = $serverName - name = $appName - applicationName = $appName - description = "$appName for local development with Steeltoe" - language = "java" - dependencies = $dependencies - version = $serverVersion - } ` - -OutFile $artifactName + Copy-Item -Path $sourceDir -Destination $serverName -Recurse -Force + + # gradle-wrapper.jar is not committed to source; download it from the Gradle GitHub repo + $wrapperJarPath = Join-Path $serverName "gradle" "wrapper" "gradle-wrapper.jar" + $wrapperPropertiesPath = Join-Path $serverName "gradle" "wrapper" "gradle-wrapper.properties" + $wrapperPropertiesContent = Get-Content $wrapperPropertiesPath -Raw + if ($wrapperPropertiesContent -match 'distributionUrl=.*gradle-(\d+(?:\.\d+)+)-') { + $gradleVersion = $Matches[1] } else { - Write-Host "Using cached download from start.spring.io ($artifactName)" + throw "Could not determine Gradle version from $wrapperPropertiesPath" + } + + if (!(Test-Path $wrapperJarPath)) { + Write-Host "Downloading gradle-wrapper.jar for Gradle $gradleVersion..." + Invoke-WebRequest ` + -Uri "https://raw.githubusercontent.com/gradle/gradle/v$gradleVersion/gradle/wrapper/gradle-wrapper.jar" ` + -OutFile $wrapperJarPath ` + -UseBasicParsing } - New-Item -ItemType Directory -Path $serverName | Out-Null - Expand-Archive -Path $artifactName -DestinationPath $serverName -Force + # Validate the wrapper jar against Gradle's published checksum so a tampered or + # truncated download can never run on a build machine (supply-chain integrity). + $shaContent = (Invoke-WebRequest ` + -Uri "https://services.gradle.org/distributions/gradle-$gradleVersion-wrapper.jar.sha256" ` + -UseBasicParsing).Content + if ($shaContent -is [byte[]]) { + $shaContent = [System.Text.Encoding]::ASCII.GetString($shaContent) + } + $expectedSha = $shaContent.Trim().ToLower() + $actualSha = (Get-FileHash -Algorithm SHA256 -Path $wrapperJarPath).Hash.ToLower() + if ($actualSha -ne $expectedSha) { + throw "gradle-wrapper.jar checksum mismatch for Gradle $gradleVersion (expected $expectedSha, got $actualSha)" + } + Write-Host "Verified gradle-wrapper.jar checksum ($actualSha)" Push-Location $serverName try { - # Apply patches - foreach ($patch in Get-ChildItem -Path (Join-Path $ImageDirectory patches) -Filter "*.patch") { - Write-Host "Applying patch $($patch.Name)" - git apply --unidiff-zero --recount --ignore-whitespace $patch.FullName - if ($LASTEXITCODE -ne 0) { - throw "Patch $($patch.Name) failed with exit code $LASTEXITCODE" - } - Write-Host "Patch $($patch.Name) applied successfully" + # Ensure gradlew is executable (git does not preserve the execute bit on Windows) + if ($IsLinux -or $IsMacOS) { + & chmod +x gradlew } # Build the image + # Use the commit timestamp as the image creation date so identical source + # produces an identical image digest. Falls back to the build.gradle default + # (a fixed epoch) when git or commit metadata is unavailable. $gradleArgs = @("bootBuildImage", "--imageName=$ImageNameWithTag") + if (Get-Command git -ErrorAction SilentlyContinue) { + $createdDate = (& git -C $ImagesDirectory show -s --format=%cI HEAD 2>$null) + if ($LASTEXITCODE -eq 0 -and $createdDate) { + $gradleArgs += "-PimageCreatedDate=$($createdDate.Trim())" + } + } if ($env:GITHUB_ACTIONS -eq "true") { $gradleArgs += "--no-daemon" } @@ -250,11 +234,6 @@ try { finally { Pop-Location } - - foreach ($AdditionalTag in $AdditionalTags.Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)) { - Write-Host "Running 'docker tag $ImageNameWithTag $AdditionalTag'" - docker tag $ImageNameWithTag $AdditionalTag - } } finally { Pop-Location # workspace diff --git a/config-server/customizations/build.gradle.append b/config-server/customizations/build.gradle.append new file mode 100644 index 0000000..73ecc11 --- /dev/null +++ b/config-server/customizations/build.gradle.append @@ -0,0 +1,22 @@ +dependencyLocking { + lockAllConfigurations() +} + +bootBuildImage { + // Pin the builder and run image by digest so image contents are reproducible and + // base-image updates become an explicit, reviewable change rather than silent drift. + builder = "paketobuildpacks/builder-noble-java-tiny@sha256:38e94b8533e158f201f17d1ad733d47df75728f80b640c4ebc5b9f2ebe831601" + runImage = "paketobuildpacks/ubuntu-noble-run-tiny@sha256:941d6d5324318352f08bd8e3af2c80ab7822f250b88ae4ecc22299c5367887a6" + // A fixed creation timestamp keeps the image digest reproducible. CI overrides this with + // the commit timestamp via -PimageCreatedDate; the fixed epoch is the reproducible default. + createdDate = project.findProperty('imageCreatedDate') ?: '1980-01-01T00:00:00Z' + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", + "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} + +// Never build an image from code that fails its tests. +tasks.named('bootBuildImage') { + dependsOn 'test' +} diff --git a/config-server/customizations/overlay/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java b/config-server/customizations/overlay/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java new file mode 100644 index 0000000..6d5df60 --- /dev/null +++ b/config-server/customizations/overlay/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java @@ -0,0 +1,62 @@ +package io.steeltoe.docker.configserver; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class BasicOrNoAuthConfig { + + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") + public UserDetailsService userDetailsService( + @Value("${auth.username:devuser}") String username, + @Value("${auth.password:devpassword}") String password) { + + return new InMemoryUserDetailsManager( + User.withUsername(username) + .password(password) + .roles("USER") + .build()); + } + + @SuppressWarnings("deprecation") + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") + public PasswordEncoder passwordEncoder() { + // For dev/test only — plaintext password + return NoOpPasswordEncoder.getInstance(); + } + + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") + @Order(SecurityProperties.BASIC_AUTH_ORDER) + public SecurityFilterChain basicAuthChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()); + return http.build(); + } + + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "false", matchIfMissing = true) + @Order(SecurityProperties.BASIC_AUTH_ORDER) + public SecurityFilterChain permitAllChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .csrf(csrf -> csrf.disable()); + return http.build(); + } +} diff --git a/config-server/customizations/overlay/src/test/java/io/steeltoe/docker/configserver/ConfigServerAuthEnabledTests.java b/config-server/customizations/overlay/src/test/java/io/steeltoe/docker/configserver/ConfigServerAuthEnabledTests.java new file mode 100644 index 0000000..19b0b0e --- /dev/null +++ b/config-server/customizations/overlay/src/test/java/io/steeltoe/docker/configserver/ConfigServerAuthEnabledTests.java @@ -0,0 +1,47 @@ +package io.steeltoe.docker.configserver; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * With {@code auth.enabled=true}, {@code BasicOrNoAuthConfig} wires the HTTP Basic + * security chain plus an in-memory user. This verifies that custom logic end to end: + * unauthenticated requests are rejected and valid credentials are accepted. + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { + "auth.enabled=true", + "auth.username=tester", + "auth.password=secret", + "spring.cloud.config.server.health.enabled=false", + "spring.cloud.config.server.git.cloneOnStart=false" + }) +class ConfigServerAuthEnabledTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void rejectsRequestsWithoutCredentials() { + ResponseEntity response = restTemplate.getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + @Test + void acceptsRequestsWithValidCredentials() { + ResponseEntity response = restTemplate + .withBasicAuth("tester", "secret") + .getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } +} diff --git a/config-server/customizations/overlay/src/test/java/io/steeltoe/docker/configserver/ConfigServerTests.java b/config-server/customizations/overlay/src/test/java/io/steeltoe/docker/configserver/ConfigServerTests.java new file mode 100644 index 0000000..8b6f890 --- /dev/null +++ b/config-server/customizations/overlay/src/test/java/io/steeltoe/docker/configserver/ConfigServerTests.java @@ -0,0 +1,39 @@ +package io.steeltoe.docker.configserver; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Default configuration: {@code auth.enabled=false}, so {@code BasicOrNoAuthConfig} + * wires the permit-all security chain. The config-server health contributor is + * disabled here so the test never reaches out to the configured git repository. + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { + "spring.cloud.config.server.health.enabled=false", + "spring.cloud.config.server.git.cloneOnStart=false" + }) +class ConfigServerTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + } + + @Test + void healthIsOpenWhenAuthDisabled() { + ResponseEntity response = restTemplate.getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } +} diff --git a/config-server/metadata/ADDITIONAL_TAGS b/config-server/metadata/ADDITIONAL_TAGS deleted file mode 100644 index abba65b..0000000 --- a/config-server/metadata/ADDITIONAL_TAGS +++ /dev/null @@ -1 +0,0 @@ -config-server:4.3 config-server:4 config-server:latest diff --git a/config-server/metadata/IMAGE_REVISION b/config-server/metadata/IMAGE_REVISION index 8b13789..e69de29 100644 --- a/config-server/metadata/IMAGE_REVISION +++ b/config-server/metadata/IMAGE_REVISION @@ -1 +0,0 @@ - diff --git a/config-server/metadata/IMAGE_VERSION b/config-server/metadata/IMAGE_VERSION index f77856a..cc2fbe8 100644 --- a/config-server/metadata/IMAGE_VERSION +++ b/config-server/metadata/IMAGE_VERSION @@ -1 +1 @@ -4.3.1 +4.3.2 diff --git a/config-server/metadata/PORT b/config-server/metadata/PORT new file mode 100644 index 0000000..5246073 --- /dev/null +++ b/config-server/metadata/PORT @@ -0,0 +1 @@ +8888 diff --git a/config-server/metadata/SPRING_BOOT_VERSION b/config-server/metadata/SPRING_BOOT_VERSION index efe3085..3781bee 100644 --- a/config-server/metadata/SPRING_BOOT_VERSION +++ b/config-server/metadata/SPRING_BOOT_VERSION @@ -1 +1 @@ -3.5.10 +3.5.14 diff --git a/config-server/patches/addauth.patch b/config-server/patches/addauth.patch deleted file mode 100644 index 3aa694e..0000000 --- a/config-server/patches/addauth.patch +++ /dev/null @@ -1,65 +0,0 @@ ---- /dev/null -+++ configserver/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java 2025-08-15 13:15:18.461432100 -0500 -@@ -0,0 +1,62 @@ -+package io.steeltoe.docker.configserver; -+ -+import org.springframework.beans.factory.annotation.Value; -+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -+import org.springframework.boot.autoconfigure.security.SecurityProperties; -+import org.springframework.context.annotation.Bean; -+import org.springframework.context.annotation.Configuration; -+import org.springframework.core.annotation.Order; -+import org.springframework.security.config.Customizer; -+import org.springframework.security.config.annotation.web.builders.HttpSecurity; -+import org.springframework.security.core.userdetails.User; -+import org.springframework.security.core.userdetails.UserDetailsService; -+import org.springframework.security.crypto.password.NoOpPasswordEncoder; -+import org.springframework.security.crypto.password.PasswordEncoder; -+import org.springframework.security.provisioning.InMemoryUserDetailsManager; -+import org.springframework.security.web.SecurityFilterChain; -+ -+@Configuration -+public class BasicOrNoAuthConfig { -+ -+ @Bean -+ @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") -+ public UserDetailsService userDetailsService( -+ @Value("${auth.username:devuser}") String username, -+ @Value("${auth.password:devpassword}") String password) { -+ -+ return new InMemoryUserDetailsManager( -+ User.withUsername(username) -+ .password(password) -+ .roles("USER") -+ .build()); -+ } -+ -+ @SuppressWarnings("deprecation") -+ @Bean -+ @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") -+ public PasswordEncoder passwordEncoder() { -+ // For dev/test only — plaintext password -+ return NoOpPasswordEncoder.getInstance(); -+ } -+ -+ @Bean -+ @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") -+ @Order(SecurityProperties.BASIC_AUTH_ORDER) -+ public SecurityFilterChain basicAuthChain(HttpSecurity http) throws Exception { -+ http -+ .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) -+ .httpBasic(Customizer.withDefaults()) -+ .csrf(csrf -> csrf.disable()); -+ return http.build(); -+ } -+ -+ @Bean -+ @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "false", matchIfMissing = true) -+ @Order(SecurityProperties.BASIC_AUTH_ORDER) -+ public SecurityFilterChain permitAllChain(HttpSecurity http) throws Exception { -+ http -+ .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) -+ .csrf(csrf -> csrf.disable()); -+ return http.build(); -+ } -+} diff --git a/config-server/patches/application.properties.patch b/config-server/patches/application.properties.patch index cda4bd4..0e1d748 100644 --- a/config-server/patches/application.properties.patch +++ b/config-server/patches/application.properties.patch @@ -1,6 +1,6 @@ --- configserver/src/main/resources/application.properties 2024-02-21 15:43:09.000000000 -0600 -+++ configserver/src/main/resources/application.properties 2025-08-15 13:15:18.461432100 -0500 -@@ -0,0 +1,10 @@ ++++ configserver/src/main/resources/application.properties 2026-01-27 00:00:00.000000000 -0500 +@@ -0,0 +1,11 @@ +server.port=8888 +spring.cloud.config.server.git.uri=https://github.com/spring-cloud-samples/config-repo +eureka.client.enabled=false @@ -11,3 +11,4 @@ +eureka.instance.hostname=host.docker.internal +eureka.instance.instanceId=host.docker.internal:configserver:8888 +auth.enabled=false ++logging.level.io.steeltoe.docker=INFO diff --git a/config-server/patches/build.gradle.patch b/config-server/patches/build.gradle.patch deleted file mode 100644 index e72ffbe..0000000 --- a/config-server/patches/build.gradle.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- ./build.gradle 2025-09-30 14:48:20.000000000 -0500 -+++ ./build.gradle 2025-09-30 14:49:16.584226000 -0500 -@@ -41,3 +41,11 @@ - tasks.named('test') { - useJUnitPlatform() - } -+ -+bootBuildImage { -+ createdDate = "now" -+ environment = [ -+ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", -+ "BP_JVM_AOTCACHE_ENABLED": "true" -+ ] -+} diff --git a/config-server/patches/enableconfigserver.patch b/config-server/patches/enableconfigserver.patch index c971079..7ffc6be 100644 --- a/config-server/patches/enableconfigserver.patch +++ b/config-server/patches/enableconfigserver.patch @@ -1,6 +1,6 @@ --- configserver/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java 2024-02-21 13:33:04.000000000 -0600 -+++ configserver/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java 2024-04-02 13:40:40.622446300 -0600 -@@ -1,12 +1,22 @@ ++++ configserver/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java 2026-01-27 00:00:00.000000000 -0600 +@@ -1,10 +1,20 @@ package io.steeltoe.docker.configserver; +import org.slf4j.Logger; @@ -21,4 +21,3 @@ + Package pkg = EnableConfigServer.class.getPackage(); + logger.info("{} {} by {}", pkg.getImplementationTitle(), pkg.getImplementationVersion(), pkg.getImplementationVendor()); SpringApplication.run(ConfigServer.class, args); - } diff --git a/config-server/source/build.gradle b/config-server/source/build.gradle new file mode 100644 index 0000000..6215409 --- /dev/null +++ b/config-server/source/build.gradle @@ -0,0 +1,66 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.5.14' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'io.steeltoe.docker' +version = '4.3.2' +description = 'ConfigServer for local development with Steeltoe' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + +repositories { + mavenCentral() +} + +ext { + set('springCloudVersion', "2025.0.2") +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.cloud:spring-cloud-config-server' + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + +tasks.named('test') { + useJUnitPlatform() +} + +dependencyLocking { + lockAllConfigurations() +} + +bootBuildImage { + // Pin the builder and run image by digest so image contents are reproducible and + // base-image updates become an explicit, reviewable change rather than silent drift. + builder = "paketobuildpacks/builder-noble-java-tiny@sha256:38e94b8533e158f201f17d1ad733d47df75728f80b640c4ebc5b9f2ebe831601" + runImage = "paketobuildpacks/ubuntu-noble-run-tiny@sha256:941d6d5324318352f08bd8e3af2c80ab7822f250b88ae4ecc22299c5367887a6" + // A fixed creation timestamp keeps the image digest reproducible. CI overrides this with + // the commit timestamp via -PimageCreatedDate; the fixed epoch is the reproducible default. + createdDate = project.findProperty('imageCreatedDate') ?: '1980-01-01T00:00:00Z' + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", + "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} + +// Never build an image from code that fails its tests. +tasks.named('bootBuildImage') { + dependsOn 'test' +} diff --git a/config-server/source/gradle.lockfile b/config-server/source/gradle.lockfile new file mode 100644 index 0000000..d6c52db --- /dev/null +++ b/config-server/source/gradle.lockfile @@ -0,0 +1,140 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +antlr:antlr:2.7.7=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.5.32=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.5.32=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml:classmate:1.7.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.41.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.googlecode.javaewah:JavaEWAH:1.2.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testRuntimeClasspath +com.netflix.eureka:eureka-client:2.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.netflix.netflix-commons:netflix-eventbus:0.3.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.netflix.netflix-commons:netflix-infix:0.3.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.netflix.servo:servo-core:0.5.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.netflix.spectator:spectator-api:1.7.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.stoyanr:evictor:1.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.xstream:xstream:1.4.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.vaadin.external.google:android-json:0.0.20131108.vaadin1=testCompileClasspath,testRuntimeClasspath +commons-codec:commons-codec:1.18.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-jxpath:commons-jxpath:1.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +commons-logging:commons-logging:1.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.github.x-stream:mxparser:1.2.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-commons:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-core:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-jakarta9:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-observation:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor.addons:reactor-extra:3.5.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor:reactor-core:3.7.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.4=testCompileClasspath,testRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:2.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.validation:jakarta.validation-api:3.0.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.ws.rs:jakarta.ws.rs-api:3.1.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=testCompileClasspath,testRuntimeClasspath +javax.annotation:javax.annotation-api:1.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +joda-time:joda-time:2.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.17.8=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=testCompileClasspath,testRuntimeClasspath +net.i2p.crypto:eddsa:0.3.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.minidev:accessors-smart:2.5.2=testCompileClasspath,testRuntimeClasspath +net.minidev:json-smart:2.5.2=testCompileClasspath,testRuntimeClasspath +org.antlr:antlr-runtime:3.4=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.antlr:stringtemplate:3.2.1=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.apache.commons:commons-math:2.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpclient:4.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpcore:4.4.16=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.24.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.24.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.sshd:sshd-osgi:2.14.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.sshd:sshd-sftp:2.14.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-core:10.1.54=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-el:10.1.54=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-websocket:10.1.54=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath +org.awaitility:awaitility:4.2.2=testCompileClasspath,testRuntimeClasspath +org.bouncycastle:bcprov-jdk18on:1.80=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.jettison:jettison:1.5.4=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.eclipse.jgit:org.eclipse.jgit.http.apache:6.10.1.202505221210-r=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.1.202505221210-r=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jgit:org.eclipse.jgit:6.10.1.202505221210-r=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.hibernate.validator:hibernate-validator:8.0.3.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.12.2=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.12.2=testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.12.2=testRuntimeClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath,testRuntimeClasspath +org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.mockito:mockito-core:5.17.0=testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-junit-jupiter:5.17.0=testCompileClasspath,testRuntimeClasspath +org.objenesis:objenesis:3.3=testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm:9.7.1=testCompileClasspath,testRuntimeClasspath +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.skyscreamer:jsonassert:1.5.3=testCompileClasspath,testRuntimeClasspath +org.slf4j:jcl-over-slf4j:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-actuator:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-cache:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-json:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-security:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-test:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-validation:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-web:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test-autoconfigure:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-commons:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-config-client:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-config-server:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-context:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-loadbalancer:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-netflix-eureka-client:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-starter-loadbalancer:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-starter:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-config:6.5.10=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-core:6.5.10=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-crypto:6.5.10=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-test:6.5.10=testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-web:6.5.10=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-aop:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-beans:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context-support:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-core:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-expression:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-jcl:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-test:6.2.18=testCompileClasspath,testRuntimeClasspath +org.springframework:spring-web:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-webmvc:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlunit:xmlunit-core:2.10.4=testCompileClasspath,testRuntimeClasspath +org.yaml:snakeyaml:2.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +xmlpull:xmlpull:1.1.3.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +empty=annotationProcessor,developmentOnly,testAndDevelopmentOnly,testAnnotationProcessor diff --git a/config-server/source/gradle/wrapper/gradle-wrapper.properties b/config-server/source/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aaaabb3 --- /dev/null +++ b/config-server/source/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/config-server/source/gradlew b/config-server/source/gradlew new file mode 100755 index 0000000..23d15a9 --- /dev/null +++ b/config-server/source/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/config-server/source/gradlew.bat b/config-server/source/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/config-server/source/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/config-server/source/settings.gradle b/config-server/source/settings.gradle new file mode 100644 index 0000000..39a370e --- /dev/null +++ b/config-server/source/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'configserver' diff --git a/config-server/source/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java b/config-server/source/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java new file mode 100644 index 0000000..6d5df60 --- /dev/null +++ b/config-server/source/src/main/java/io/steeltoe/docker/configserver/BasicOrNoAuthConfig.java @@ -0,0 +1,62 @@ +package io.steeltoe.docker.configserver; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class BasicOrNoAuthConfig { + + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") + public UserDetailsService userDetailsService( + @Value("${auth.username:devuser}") String username, + @Value("${auth.password:devpassword}") String password) { + + return new InMemoryUserDetailsManager( + User.withUsername(username) + .password(password) + .roles("USER") + .build()); + } + + @SuppressWarnings("deprecation") + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") + public PasswordEncoder passwordEncoder() { + // For dev/test only — plaintext password + return NoOpPasswordEncoder.getInstance(); + } + + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "true") + @Order(SecurityProperties.BASIC_AUTH_ORDER) + public SecurityFilterChain basicAuthChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()) + .csrf(csrf -> csrf.disable()); + return http.build(); + } + + @Bean + @ConditionalOnProperty(prefix = "auth", name = "enabled", havingValue = "false", matchIfMissing = true) + @Order(SecurityProperties.BASIC_AUTH_ORDER) + public SecurityFilterChain permitAllChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .csrf(csrf -> csrf.disable()); + return http.build(); + } +} diff --git a/config-server/source/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java b/config-server/source/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java new file mode 100644 index 0000000..f71a827 --- /dev/null +++ b/config-server/source/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java @@ -0,0 +1,23 @@ +package io.steeltoe.docker.configserver; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.config.server.EnableConfigServer; + +@SpringBootApplication +@EnableConfigServer +@EnableDiscoveryClient +public class ConfigServer { + + private static final Logger logger = LoggerFactory.getLogger(ConfigServer.class); + + public static void main(String[] args) { + Package pkg = EnableConfigServer.class.getPackage(); + logger.info("{} {} by {}", pkg.getImplementationTitle(), pkg.getImplementationVersion(), pkg.getImplementationVendor()); + SpringApplication.run(ConfigServer.class, args); + } + +} diff --git a/config-server/source/src/main/resources/application.properties b/config-server/source/src/main/resources/application.properties new file mode 100644 index 0000000..0d3d1c1 --- /dev/null +++ b/config-server/source/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=8888 +spring.cloud.config.server.git.uri=https://github.com/spring-cloud-samples/config-repo +eureka.client.enabled=false +eureka.client.fetchRegistry=false +eureka.client.serviceUrl.defaultZone=http://host.docker.internal:8761/eureka +eureka.instance.appname=configserver +eureka.instance.virtualhostname=configserver +eureka.instance.hostname=host.docker.internal +eureka.instance.instanceId=host.docker.internal:configserver:8888 +auth.enabled=false +logging.level.io.steeltoe.docker=INFO +spring.application.name=ConfigServer diff --git a/config-server/source/src/test/java/io/steeltoe/docker/configserver/ConfigServerAuthEnabledTests.java b/config-server/source/src/test/java/io/steeltoe/docker/configserver/ConfigServerAuthEnabledTests.java new file mode 100644 index 0000000..19b0b0e --- /dev/null +++ b/config-server/source/src/test/java/io/steeltoe/docker/configserver/ConfigServerAuthEnabledTests.java @@ -0,0 +1,47 @@ +package io.steeltoe.docker.configserver; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * With {@code auth.enabled=true}, {@code BasicOrNoAuthConfig} wires the HTTP Basic + * security chain plus an in-memory user. This verifies that custom logic end to end: + * unauthenticated requests are rejected and valid credentials are accepted. + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { + "auth.enabled=true", + "auth.username=tester", + "auth.password=secret", + "spring.cloud.config.server.health.enabled=false", + "spring.cloud.config.server.git.cloneOnStart=false" + }) +class ConfigServerAuthEnabledTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void rejectsRequestsWithoutCredentials() { + ResponseEntity response = restTemplate.getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + @Test + void acceptsRequestsWithValidCredentials() { + ResponseEntity response = restTemplate + .withBasicAuth("tester", "secret") + .getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } +} diff --git a/config-server/source/src/test/java/io/steeltoe/docker/configserver/ConfigServerTests.java b/config-server/source/src/test/java/io/steeltoe/docker/configserver/ConfigServerTests.java new file mode 100644 index 0000000..8b6f890 --- /dev/null +++ b/config-server/source/src/test/java/io/steeltoe/docker/configserver/ConfigServerTests.java @@ -0,0 +1,39 @@ +package io.steeltoe.docker.configserver; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Default configuration: {@code auth.enabled=false}, so {@code BasicOrNoAuthConfig} + * wires the permit-all security chain. The config-server health contributor is + * disabled here so the test never reaches out to the configured git repository. + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { + "spring.cloud.config.server.health.enabled=false", + "spring.cloud.config.server.git.cloneOnStart=false" + }) +class ConfigServerTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + } + + @Test + void healthIsOpenWhenAuthDisabled() { + ResponseEntity response = restTemplate.getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } +} diff --git a/eureka-server/customizations/build.gradle.append b/eureka-server/customizations/build.gradle.append new file mode 100644 index 0000000..73ecc11 --- /dev/null +++ b/eureka-server/customizations/build.gradle.append @@ -0,0 +1,22 @@ +dependencyLocking { + lockAllConfigurations() +} + +bootBuildImage { + // Pin the builder and run image by digest so image contents are reproducible and + // base-image updates become an explicit, reviewable change rather than silent drift. + builder = "paketobuildpacks/builder-noble-java-tiny@sha256:38e94b8533e158f201f17d1ad733d47df75728f80b640c4ebc5b9f2ebe831601" + runImage = "paketobuildpacks/ubuntu-noble-run-tiny@sha256:941d6d5324318352f08bd8e3af2c80ab7822f250b88ae4ecc22299c5367887a6" + // A fixed creation timestamp keeps the image digest reproducible. CI overrides this with + // the commit timestamp via -PimageCreatedDate; the fixed epoch is the reproducible default. + createdDate = project.findProperty('imageCreatedDate') ?: '1980-01-01T00:00:00Z' + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", + "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} + +// Never build an image from code that fails its tests. +tasks.named('bootBuildImage') { + dependsOn 'test' +} diff --git a/eureka-server/customizations/overlay/src/test/java/io/steeltoe/docker/eurekaserver/EurekaServerTests.java b/eureka-server/customizations/overlay/src/test/java/io/steeltoe/docker/eurekaserver/EurekaServerTests.java new file mode 100644 index 0000000..9a2f900 --- /dev/null +++ b/eureka-server/customizations/overlay/src/test/java/io/steeltoe/docker/eurekaserver/EurekaServerTests.java @@ -0,0 +1,34 @@ +package io.steeltoe.docker.eurekaserver; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The Eureka server has no custom logic, so the useful check is that it boots with the + * committed dependencies and configuration and reports a healthy actuator endpoint. + * This is the regression signal we want when bumping Spring Boot / Spring Cloud versions. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class EurekaServerTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + } + + @Test + void healthEndpointReportsUp() { + ResponseEntity response = restTemplate.getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } +} diff --git a/eureka-server/metadata/ADDITIONAL_TAGS b/eureka-server/metadata/ADDITIONAL_TAGS deleted file mode 100644 index 2b842e6..0000000 --- a/eureka-server/metadata/ADDITIONAL_TAGS +++ /dev/null @@ -1 +0,0 @@ -eureka-server:4.3 eureka-server:4 eureka-server:latest diff --git a/eureka-server/metadata/IMAGE_VERSION b/eureka-server/metadata/IMAGE_VERSION index f77856a..cc2fbe8 100644 --- a/eureka-server/metadata/IMAGE_VERSION +++ b/eureka-server/metadata/IMAGE_VERSION @@ -1 +1 @@ -4.3.1 +4.3.2 diff --git a/eureka-server/metadata/PORT b/eureka-server/metadata/PORT new file mode 100644 index 0000000..9e33578 --- /dev/null +++ b/eureka-server/metadata/PORT @@ -0,0 +1 @@ +8761 diff --git a/eureka-server/metadata/SPRING_BOOT_VERSION b/eureka-server/metadata/SPRING_BOOT_VERSION index efe3085..3781bee 100644 --- a/eureka-server/metadata/SPRING_BOOT_VERSION +++ b/eureka-server/metadata/SPRING_BOOT_VERSION @@ -1 +1 @@ -3.5.10 +3.5.14 diff --git a/eureka-server/patches/application.properties.patch b/eureka-server/patches/application.properties.patch index ec109cb..7b42439 100644 --- a/eureka-server/patches/application.properties.patch +++ b/eureka-server/patches/application.properties.patch @@ -1,6 +1,6 @@ --- eurekaserver/src/main/resources/application.properties 2024-02-21 15:43:09.000000000 -0600 +++ eurekaserver/src/main/resources/application.properties 2026-01-27 00:00:00.000000000 -0500 -@@ -0,0 +1,14 @@ +@@ -0,0 +1,15 @@ +server.port = 8761 +eureka.client.fetch-registry = false +eureka.client.register-with-eureka = false @@ -14,4 +14,5 @@ +eureka.server.responseCacheUpdateIntervalMs = 1000 +eureka.server.wait-time-in-ms-when-sync-empty = 0 +logging.level.com.netflix.eureka = TRACE ++logging.level.io.steeltoe.docker = INFO +spring.main.lazy-initialization = true diff --git a/eureka-server/patches/build.gradle.patch b/eureka-server/patches/build.gradle.patch deleted file mode 100644 index e72ffbe..0000000 --- a/eureka-server/patches/build.gradle.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- ./build.gradle 2025-09-30 14:48:20.000000000 -0500 -+++ ./build.gradle 2025-09-30 14:49:16.584226000 -0500 -@@ -41,3 +41,11 @@ - tasks.named('test') { - useJUnitPlatform() - } -+ -+bootBuildImage { -+ createdDate = "now" -+ environment = [ -+ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", -+ "BP_JVM_AOTCACHE_ENABLED": "true" -+ ] -+} diff --git a/eureka-server/source/build.gradle b/eureka-server/source/build.gradle new file mode 100644 index 0000000..84ed7d4 --- /dev/null +++ b/eureka-server/source/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.5.14' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'io.steeltoe.docker' +version = '4.3.2' +description = 'EurekaServer for local development with Steeltoe' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + +repositories { + mavenCentral() +} + +ext { + set('springCloudVersion', "2025.0.2") +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + +tasks.named('test') { + useJUnitPlatform() +} + +dependencyLocking { + lockAllConfigurations() +} + +bootBuildImage { + // Pin the builder and run image by digest so image contents are reproducible and + // base-image updates become an explicit, reviewable change rather than silent drift. + builder = "paketobuildpacks/builder-noble-java-tiny@sha256:38e94b8533e158f201f17d1ad733d47df75728f80b640c4ebc5b9f2ebe831601" + runImage = "paketobuildpacks/ubuntu-noble-run-tiny@sha256:941d6d5324318352f08bd8e3af2c80ab7822f250b88ae4ecc22299c5367887a6" + // A fixed creation timestamp keeps the image digest reproducible. CI overrides this with + // the commit timestamp via -PimageCreatedDate; the fixed epoch is the reproducible default. + createdDate = project.findProperty('imageCreatedDate') ?: '1980-01-01T00:00:00Z' + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", + "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} + +// Never build an image from code that fails its tests. +tasks.named('bootBuildImage') { + dependsOn 'test' +} diff --git a/eureka-server/source/gradle.lockfile b/eureka-server/source/gradle.lockfile new file mode 100644 index 0000000..5dfcfe2 --- /dev/null +++ b/eureka-server/source/gradle.lockfile @@ -0,0 +1,150 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +antlr:antlr:2.7.7=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.5.32=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.5.32=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.woodstox:woodstox-core:7.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.41.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.google.guava:failureaccess:1.0.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.google.guava:guava:33.0.0-jre=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testRuntimeClasspath +com.netflix.eureka:eureka-client-jersey3:2.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.netflix.eureka:eureka-client:2.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.netflix.eureka:eureka-core-jersey3:2.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.netflix.eureka:eureka-core:2.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.netflix.netflix-commons:netflix-eventbus:0.3.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.netflix.netflix-commons:netflix-infix:0.3.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.netflix.servo:servo-core:0.5.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +com.netflix.spectator:spectator-api:1.7.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.stoyanr:evictor:1.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.sun.istack:istack-commons-runtime:4.1.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.xstream:xstream:1.4.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.vaadin.external.google:android-json:0.0.20131108.vaadin1=testCompileClasspath,testRuntimeClasspath +commons-codec:commons-codec:1.18.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-jxpath:commons-jxpath:1.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +io.github.x-stream:mxparser:1.2.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-commons:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-core:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-jakarta9:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-observation:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor.addons:reactor-extra:3.5.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor:reactor-core:3.7.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:2.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.validation:jakarta.validation-api:3.0.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.ws.rs:jakarta.ws.rs-api:3.1.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.annotation:javax.annotation-api:1.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +joda-time:joda-time:2.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.17.8=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=testCompileClasspath,testRuntimeClasspath +net.minidev:accessors-smart:2.5.2=testCompileClasspath,testRuntimeClasspath +net.minidev:json-smart:2.5.2=testCompileClasspath,testRuntimeClasspath +org.antlr:antlr-runtime:3.4=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.antlr:stringtemplate:3.2.1=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.apache.commons:commons-math:2.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpclient:4.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpcore:4.4.16=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.24.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.24.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-core:10.1.54=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-el:10.1.54=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-websocket:10.1.54=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath +org.awaitility:awaitility:4.2.2=testCompileClasspath,testRuntimeClasspath +org.bouncycastle:bcprov-jdk18on:1.80=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.checkerframework:checker-qual:3.41.0=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.codehaus.jettison:jettison:1.5.4=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.codehaus.woodstox:stax2-api:4.2.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.angus:angus-activation:2.0.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.freemarker:freemarker:2.3.34=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.hk2:hk2-api:3.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.hk2:hk2-locator:3.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.hk2:hk2-utils:3.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.hk2:osgi-resource-locator:1.0.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.hk2:spring-bridge:3.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jaxb:jaxb-core:4.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jaxb:jaxb-runtime:4.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jaxb:txw2:4.0.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.connectors:jersey-apache-connector:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.containers:jersey-container-servlet-core:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.containers:jersey-container-servlet:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.core:jersey-client:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.core:jersey-common:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.core:jersey-server:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.glassfish.jersey.inject:jersey-hk2:3.1.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.javassist:javassist:3.30.2-GA=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.12.2=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.12.2=testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.12.2=testRuntimeClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath,testRuntimeClasspath +org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.mockito:mockito-core:5.17.0=testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-junit-jupiter:5.17.0=testCompileClasspath,testRuntimeClasspath +org.objenesis:objenesis:3.3=testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm:9.7.1=testCompileClasspath,testRuntimeClasspath +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.skyscreamer:jsonassert:1.5.3=testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-actuator:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-cache:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-freemarker:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-json:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-test:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-web:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test-autoconfigure:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-commons:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-context:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-loadbalancer:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-netflix-eureka-client:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-netflix-eureka-server:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-starter-loadbalancer:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-starter-netflix-eureka-server:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-starter:4.3.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-crypto:6.5.10=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-aop:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-beans:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context-support:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-core:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-expression:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-jcl:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-test:6.2.18=testCompileClasspath,testRuntimeClasspath +org.springframework:spring-web:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-webmvc:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlunit:xmlunit-core:2.10.4=testCompileClasspath,testRuntimeClasspath +org.yaml:snakeyaml:2.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +xmlpull:xmlpull:1.1.3.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +empty=annotationProcessor,developmentOnly,testAndDevelopmentOnly,testAnnotationProcessor diff --git a/eureka-server/source/gradle/wrapper/gradle-wrapper.properties b/eureka-server/source/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aaaabb3 --- /dev/null +++ b/eureka-server/source/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/eureka-server/source/gradlew b/eureka-server/source/gradlew new file mode 100755 index 0000000..23d15a9 --- /dev/null +++ b/eureka-server/source/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/eureka-server/source/gradlew.bat b/eureka-server/source/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/eureka-server/source/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/eureka-server/source/settings.gradle b/eureka-server/source/settings.gradle new file mode 100644 index 0000000..050bb32 --- /dev/null +++ b/eureka-server/source/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'eurekaserver' diff --git a/eureka-server/source/src/main/java/io/steeltoe/docker/eurekaserver/EurekaServer.java b/eureka-server/source/src/main/java/io/steeltoe/docker/eurekaserver/EurekaServer.java new file mode 100644 index 0000000..34c9aff --- /dev/null +++ b/eureka-server/source/src/main/java/io/steeltoe/docker/eurekaserver/EurekaServer.java @@ -0,0 +1,21 @@ +package io.steeltoe.docker.eurekaserver; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; + +@SpringBootApplication +@EnableEurekaServer +public class EurekaServer { + + private static final Logger logger = LoggerFactory.getLogger(EurekaServer.class); + + public static void main(String[] args) { + Package pkg = EnableEurekaServer.class.getPackage(); + logger.info("{} {} by {}", pkg.getImplementationTitle(), pkg.getImplementationVersion(), pkg.getImplementationVendor()); + SpringApplication.run(EurekaServer.class, args); + } + +} diff --git a/eureka-server/source/src/main/resources/application.properties b/eureka-server/source/src/main/resources/application.properties new file mode 100644 index 0000000..3892e4f --- /dev/null +++ b/eureka-server/source/src/main/resources/application.properties @@ -0,0 +1,16 @@ +server.port = 8761 +eureka.client.fetch-registry = false +eureka.client.register-with-eureka = false +eureka.client.serviceUrl.defaultZone = ${EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/} +eureka.instance.hostname = localhost +# Set myUrl to match defaultZone so Eureka recognizes itself and skips self-replication +eureka.server.myUrl = ${EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/} +eureka.server.enable-self-preservation = false +eureka.server.numberOfReplicationRetries = 0 +eureka.server.evictionIntervalTimerInMs = 1000 +eureka.server.responseCacheUpdateIntervalMs = 1000 +eureka.server.wait-time-in-ms-when-sync-empty = 0 +logging.level.com.netflix.eureka = TRACE +logging.level.io.steeltoe.docker = INFO +spring.main.lazy-initialization = true +spring.application.name=EurekaServer diff --git a/eureka-server/source/src/test/java/io/steeltoe/docker/eurekaserver/EurekaServerTests.java b/eureka-server/source/src/test/java/io/steeltoe/docker/eurekaserver/EurekaServerTests.java new file mode 100644 index 0000000..9a2f900 --- /dev/null +++ b/eureka-server/source/src/test/java/io/steeltoe/docker/eurekaserver/EurekaServerTests.java @@ -0,0 +1,34 @@ +package io.steeltoe.docker.eurekaserver; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The Eureka server has no custom logic, so the useful check is that it boots with the + * committed dependencies and configuration and reports a healthy actuator endpoint. + * This is the regression signal we want when bumping Spring Boot / Spring Cloud versions. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class EurekaServerTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + } + + @Test + void healthEndpointReportsUp() { + ResponseEntity response = restTemplate.getForEntity("/actuator/health", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } +} diff --git a/spring-boot-admin/README.md b/spring-boot-admin/README.md index 4bebf29..bd566f7 100644 --- a/spring-boot-admin/README.md +++ b/spring-boot-admin/README.md @@ -1,4 +1,4 @@ -# steeltoeoss/spring-boot-admin +# steeltoe.azurecr.io/spring-boot-admin Image for SteeltoeOSS local development with . diff --git a/spring-boot-admin/customizations/build.gradle.append b/spring-boot-admin/customizations/build.gradle.append new file mode 100644 index 0000000..73ecc11 --- /dev/null +++ b/spring-boot-admin/customizations/build.gradle.append @@ -0,0 +1,22 @@ +dependencyLocking { + lockAllConfigurations() +} + +bootBuildImage { + // Pin the builder and run image by digest so image contents are reproducible and + // base-image updates become an explicit, reviewable change rather than silent drift. + builder = "paketobuildpacks/builder-noble-java-tiny@sha256:38e94b8533e158f201f17d1ad733d47df75728f80b640c4ebc5b9f2ebe831601" + runImage = "paketobuildpacks/ubuntu-noble-run-tiny@sha256:941d6d5324318352f08bd8e3af2c80ab7822f250b88ae4ecc22299c5367887a6" + // A fixed creation timestamp keeps the image digest reproducible. CI overrides this with + // the commit timestamp via -PimageCreatedDate; the fixed epoch is the reproducible default. + createdDate = project.findProperty('imageCreatedDate') ?: '1980-01-01T00:00:00Z' + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", + "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} + +// Never build an image from code that fails its tests. +tasks.named('bootBuildImage') { + dependsOn 'test' +} diff --git a/spring-boot-admin/customizations/overlay/src/test/java/io/steeltoe/docker/springbootadmin/SpringBootAdminTests.java b/spring-boot-admin/customizations/overlay/src/test/java/io/steeltoe/docker/springbootadmin/SpringBootAdminTests.java new file mode 100644 index 0000000..d315d89 --- /dev/null +++ b/spring-boot-admin/customizations/overlay/src/test/java/io/steeltoe/docker/springbootadmin/SpringBootAdminTests.java @@ -0,0 +1,33 @@ +package io.steeltoe.docker.springbootadmin; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Spring Boot Admin ships no actuator endpoint of its own, so the useful check is that the + * admin server boots with the committed dependencies and serves its UI. This is the + * regression signal we want when bumping Spring Boot / Spring Boot Admin versions. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class SpringBootAdminTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + } + + @Test + void adminUiIsServed() { + ResponseEntity response = restTemplate.getForEntity("/", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } +} diff --git a/spring-boot-admin/metadata/ADDITIONAL_TAGS b/spring-boot-admin/metadata/ADDITIONAL_TAGS deleted file mode 100644 index 689d059..0000000 --- a/spring-boot-admin/metadata/ADDITIONAL_TAGS +++ /dev/null @@ -1 +0,0 @@ -spring-boot-admin:3.5 spring-boot-admin:3 spring-boot-admin:latest diff --git a/spring-boot-admin/metadata/HEALTH_PATH b/spring-boot-admin/metadata/HEALTH_PATH new file mode 100644 index 0000000..b498fd4 --- /dev/null +++ b/spring-boot-admin/metadata/HEALTH_PATH @@ -0,0 +1 @@ +/ diff --git a/spring-boot-admin/metadata/IMAGE_VERSION b/spring-boot-admin/metadata/IMAGE_VERSION index 3cf5751..fa8da20 100644 --- a/spring-boot-admin/metadata/IMAGE_VERSION +++ b/spring-boot-admin/metadata/IMAGE_VERSION @@ -1 +1 @@ -3.5.7 +3.5.8 diff --git a/spring-boot-admin/metadata/PORT b/spring-boot-admin/metadata/PORT new file mode 100644 index 0000000..b874cf5 --- /dev/null +++ b/spring-boot-admin/metadata/PORT @@ -0,0 +1 @@ +9099 diff --git a/spring-boot-admin/metadata/SPRING_BOOT_VERSION b/spring-boot-admin/metadata/SPRING_BOOT_VERSION index efe3085..3781bee 100644 --- a/spring-boot-admin/metadata/SPRING_BOOT_VERSION +++ b/spring-boot-admin/metadata/SPRING_BOOT_VERSION @@ -1 +1 @@ -3.5.10 +3.5.14 diff --git a/spring-boot-admin/patches/application.properties.patch b/spring-boot-admin/patches/application.properties.patch index 4963c92..6b737a6 100644 --- a/spring-boot-admin/patches/application.properties.patch +++ b/spring-boot-admin/patches/application.properties.patch @@ -1,5 +1,6 @@ --- ./src/main/resources/application.properties 2025-10-01 14:13:49.968047867 -0500 -+++ ./src/main/resources/application.properties 2025-10-01 14:13:24.727639700 -0500 -@@ -0,0 +1,2 @@ ++++ ./src/main/resources/application.properties 2026-01-27 00:00:00.000000000 -0500 +@@ -0,0 +1,3 @@ +server.port=9099 +spring.thymeleaf.check-template-location=false ++logging.level.io.steeltoe.docker=INFO diff --git a/spring-boot-admin/patches/build.gradle.patch b/spring-boot-admin/patches/build.gradle.patch deleted file mode 100644 index 05413d3..0000000 --- a/spring-boot-admin/patches/build.gradle.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- ./build.gradle 2025-09-22 14:48:20.000000000 -0500 -+++ ./build.gradle 2026-01-27 00:00:00.000000000 -0500 -@@ -38,3 +38,11 @@ - tasks.named('test') { - useJUnitPlatform() - } -+ -+bootBuildImage { -+ createdDate = "now" -+ environment = [ -+ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", -+ "BP_JVM_AOTCACHE_ENABLED": "true" -+ ] -+} diff --git a/spring-boot-admin/patches/enable-springbootadmin.patch b/spring-boot-admin/patches/enable-springbootadmin.patch index 1208c3f..cdcb749 100644 --- a/spring-boot-admin/patches/enable-springbootadmin.patch +++ b/spring-boot-admin/patches/enable-springbootadmin.patch @@ -1,12 +1,21 @@ --- ./src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java 2024-09-20 12:49:35.099908129 -0500 -+++ ./src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java 2026-01-27 00:00:00.000000000 -0500 -@@ -2,7 +2,9 @@ - ++++ ./src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java 2026-01-27 00:00:00.000000000 +0000 +@@ -1,10 +1,18 @@ + package io.steeltoe.docker.springbootadmin; + ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import de.codecentric.boot.admin.server.config.EnableAdminServer; - + @SpringBootApplication +@EnableAdminServer public class SpringBootAdmin { - + ++ private static final Logger logger = LoggerFactory.getLogger(SpringBootAdmin.class); ++ + public static void main(String[] args) { ++ Package pkg = EnableAdminServer.class.getPackage(); ++ logger.info("{} {} by {}", pkg.getImplementationTitle(), pkg.getImplementationVersion(), pkg.getImplementationVendor()); + SpringApplication.run(SpringBootAdmin.class, args); diff --git a/spring-boot-admin/source/build.gradle b/spring-boot-admin/source/build.gradle new file mode 100644 index 0000000..446c8fb --- /dev/null +++ b/spring-boot-admin/source/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.5.14' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'io.steeltoe.docker' +version = '3.5.8' +description = 'SpringBootAdmin for local development with Steeltoe' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + +repositories { + mavenCentral() +} + +ext { + set('springBootAdminVersion', "3.5.8") +} + +dependencies { + implementation 'de.codecentric:spring-boot-admin-starter-server' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +dependencyManagement { + imports { + mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}" + } +} + +tasks.named('test') { + useJUnitPlatform() +} + +dependencyLocking { + lockAllConfigurations() +} + +bootBuildImage { + // Pin the builder and run image by digest so image contents are reproducible and + // base-image updates become an explicit, reviewable change rather than silent drift. + builder = "paketobuildpacks/builder-noble-java-tiny@sha256:38e94b8533e158f201f17d1ad733d47df75728f80b640c4ebc5b9f2ebe831601" + runImage = "paketobuildpacks/ubuntu-noble-run-tiny@sha256:941d6d5324318352f08bd8e3af2c80ab7822f250b88ae4ecc22299c5367887a6" + // A fixed creation timestamp keeps the image digest reproducible. CI overrides this with + // the commit timestamp via -PimageCreatedDate; the fixed epoch is the reproducible default. + createdDate = project.findProperty('imageCreatedDate') ?: '1980-01-01T00:00:00Z' + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", + "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} + +// Never build an image from code that fails its tests. +tasks.named('bootBuildImage') { + dependsOn 'test' +} diff --git a/spring-boot-admin/source/gradle.lockfile b/spring-boot-admin/source/gradle.lockfile new file mode 100644 index 0000000..658cfb6 --- /dev/null +++ b/spring-boot-admin/source/gradle.lockfile @@ -0,0 +1,108 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +ch.qos.logback:logback-classic:1.5.32=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.5.32=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testRuntimeClasspath +com.vaadin.external.google:android-json:0.0.20131108.vaadin1=testCompileClasspath,testRuntimeClasspath +de.codecentric:spring-boot-admin-server-cloud:3.5.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +de.codecentric:spring-boot-admin-server-ui:3.5.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +de.codecentric:spring-boot-admin-server:3.5.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +de.codecentric:spring-boot-admin-starter-server:3.5.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-commons:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-core:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-jakarta9:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-observation:1.15.11=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-dns:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http2:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-socks:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-common:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-handler-proxy:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-handler:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver-dns-classes-macos:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver-dns-native-macos:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver-dns:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-epoll:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-native-epoll:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport:4.1.132.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor.addons:reactor-extra:3.5.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor.netty:reactor-netty-core:1.2.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor.netty:reactor-netty-http:1.2.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor:reactor-core:3.7.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.4=testCompileClasspath,testRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:2.1.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.17.8=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=testCompileClasspath,testRuntimeClasspath +net.minidev:accessors-smart:2.5.2=testCompileClasspath,testRuntimeClasspath +net.minidev:json-smart:2.5.2=testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.24.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.24.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath +org.attoparser:attoparser:2.0.7.RELEASE=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.awaitility:awaitility:4.2.2=testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.12.2=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.12.2=testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.12.2=testRuntimeClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath,testRuntimeClasspath +org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath +org.mockito:mockito-core:5.17.0=testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-junit-jupiter:5.17.0=testCompileClasspath,testRuntimeClasspath +org.objenesis:objenesis:3.3=testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm:9.7.1=testCompileClasspath,testRuntimeClasspath +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.skyscreamer:jsonassert:1.5.3=testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-actuator:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-json:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-reactor-netty:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-test:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-thymeleaf:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-webflux:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test-autoconfigure:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test:3.5.14=testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot:3.5.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-aop:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-beans:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-core:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-expression:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-jcl:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-test:6.2.18=testCompileClasspath,testRuntimeClasspath +org.springframework:spring-web:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-webflux:6.2.18=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.thymeleaf:thymeleaf-spring6:3.1.5.RELEASE=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.thymeleaf:thymeleaf:3.1.5.RELEASE=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.unbescape:unbescape:1.1.6.RELEASE=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlunit:xmlunit-core:2.10.4=testCompileClasspath,testRuntimeClasspath +org.yaml:snakeyaml:2.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +empty=annotationProcessor,developmentOnly,testAndDevelopmentOnly,testAnnotationProcessor diff --git a/spring-boot-admin/source/gradle/wrapper/gradle-wrapper.properties b/spring-boot-admin/source/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aaaabb3 --- /dev/null +++ b/spring-boot-admin/source/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/spring-boot-admin/source/gradlew b/spring-boot-admin/source/gradlew new file mode 100755 index 0000000..23d15a9 --- /dev/null +++ b/spring-boot-admin/source/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/spring-boot-admin/source/gradlew.bat b/spring-boot-admin/source/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/spring-boot-admin/source/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-boot-admin/source/settings.gradle b/spring-boot-admin/source/settings.gradle new file mode 100644 index 0000000..c9d4796 --- /dev/null +++ b/spring-boot-admin/source/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'springbootadmin' diff --git a/spring-boot-admin/source/src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java b/spring-boot-admin/source/src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java new file mode 100644 index 0000000..1dc3c21 --- /dev/null +++ b/spring-boot-admin/source/src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java @@ -0,0 +1,21 @@ +package io.steeltoe.docker.springbootadmin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import de.codecentric.boot.admin.server.config.EnableAdminServer; + +@SpringBootApplication +@EnableAdminServer +public class SpringBootAdmin { + + private static final Logger logger = LoggerFactory.getLogger(SpringBootAdmin.class); + + public static void main(String[] args) { + Package pkg = EnableAdminServer.class.getPackage(); + logger.info("{} {} by {}", pkg.getImplementationTitle(), pkg.getImplementationVersion(), pkg.getImplementationVendor()); + SpringApplication.run(SpringBootAdmin.class, args); + } + +} diff --git a/spring-boot-admin/source/src/main/resources/application.properties b/spring-boot-admin/source/src/main/resources/application.properties new file mode 100644 index 0000000..e2ec6e8 --- /dev/null +++ b/spring-boot-admin/source/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port=9099 +spring.thymeleaf.check-template-location=false +logging.level.io.steeltoe.docker=INFO +spring.application.name=SpringBootAdmin diff --git a/spring-boot-admin/source/src/test/java/io/steeltoe/docker/springbootadmin/SpringBootAdminTests.java b/spring-boot-admin/source/src/test/java/io/steeltoe/docker/springbootadmin/SpringBootAdminTests.java new file mode 100644 index 0000000..d315d89 --- /dev/null +++ b/spring-boot-admin/source/src/test/java/io/steeltoe/docker/springbootadmin/SpringBootAdminTests.java @@ -0,0 +1,33 @@ +package io.steeltoe.docker.springbootadmin; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Spring Boot Admin ships no actuator endpoint of its own, so the useful check is that the + * admin server boots with the committed dependencies and serves its UI. This is the + * regression signal we want when bumping Spring Boot / Spring Boot Admin versions. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class SpringBootAdminTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + } + + @Test + void adminUiIsServed() { + ResponseEntity response = restTemplate.getForEntity("/", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } +} diff --git a/uaa-server/metadata/ADDITIONAL_TAGS b/uaa-server/metadata/ADDITIONAL_TAGS deleted file mode 100644 index 022d032..0000000 --- a/uaa-server/metadata/ADDITIONAL_TAGS +++ /dev/null @@ -1 +0,0 @@ --t uaa-server:78.3 -t uaa-server:78 -t uaa-server:latest diff --git a/uaa-server/metadata/HEALTH_PATH b/uaa-server/metadata/HEALTH_PATH new file mode 100644 index 0000000..a997f8a --- /dev/null +++ b/uaa-server/metadata/HEALTH_PATH @@ -0,0 +1 @@ +/healthz diff --git a/uaa-server/metadata/PORT b/uaa-server/metadata/PORT new file mode 100644 index 0000000..5dfa435 --- /dev/null +++ b/uaa-server/metadata/PORT @@ -0,0 +1 @@ +8080 diff --git a/update-project.ps1 b/update-project.ps1 new file mode 100644 index 0000000..ea27ecc --- /dev/null +++ b/update-project.ps1 @@ -0,0 +1,387 @@ +#!/usr/bin/env pwsh + +# ============================================================================= +# update-project.ps1: Regenerate image source from start.spring.io and apply patches +# ============================================================================= + +param ( + [String[]] $Names +) + +$ErrorActionPreference = 'Stop' + +# Cache Initializr metadata for use with all images +$script:InitializrMetadata = $null +function Get-InitializrMetadata { + if ($null -ne $script:InitializrMetadata) { + return $script:InitializrMetadata + } + + try { + $script:InitializrMetadata = Invoke-RestMethod ` + -Uri "https://start.spring.io/metadata/client" ` + -Headers @{ Accept = "application/json" } ` + -TimeoutSec 10 + } + catch { + Write-Host " (Could not fetch Initializr metadata: $_)" + } + + return $script:InitializrMetadata +} + +# Returns the current manifest list digest for the :latest tag of a Docker Hub image, +# or $null if the query fails. +function Get-BuildpackImageDigest { + param ([String] $ImageName) # format: "namespace/name" (no tag or digest suffix) + $parts = $ImageName -split '/', 2 + try { + $response = Invoke-RestMethod ` + -Uri "https://hub.docker.com/v2/repositories/$($parts[0])/$($parts[1])/tags/latest" ` + -TimeoutSec 10 + return $response.digest + } + catch { + return $null + } +} + +function Update-Project { + param ([String] $Name) + + $imagesDirectory = Split-Path -Parent $PSCommandPath + $imageDirectory = Join-Path $imagesDirectory $Name + + if (!(Test-Path $imageDirectory)) { + Write-Error "Unknown image $Name" + return + } + + if ($Name -eq "uaa-server") { + Write-Host "Skipping $Name (static Dockerfile)" + return + } + + Write-Host "Updating $Name..." + + switch ($Name) { + "config-server" { + $applicationName = "ConfigServer" + $dependencies = "cloud-config-server,actuator,cloud-eureka,security" + } + "eureka-server" { + $applicationName = "EurekaServer" + $dependencies = "cloud-eureka-server,actuator" + } + "spring-boot-admin" { + $applicationName = "SpringBootAdmin" + $dependencies = "codecentric-spring-boot-admin-server" + } + Default { + Write-Error "$Name is not supported for auto-generation" + return + } + } + + if (Test-Path (Join-Path $imageDirectory "metadata")) { + $bootVersion = Get-Content (Join-Path $imageDirectory "metadata" "SPRING_BOOT_VERSION") + $imageVersion = Get-Content (Join-Path $imageDirectory "metadata" "IMAGE_VERSION") + } + else { + Write-Error "No metadata found for $Name" + return + } + + $serverName = $Name -replace '-', '' + $javaVersion = "25" + + $encodedDescription = [Uri]::EscapeDataString("$applicationName for local development with Steeltoe") + $initializrProjectUrl = "https://start.spring.io/#!type=gradle-project&language=java" + + "&bootVersion=$bootVersion&groupId=io.steeltoe.docker&artifactId=$serverName" + + "&name=$applicationName&description=$encodedDescription&packageName=io.steeltoe.docker.$serverName" + + "&javaVersion=$javaVersion&dependencies=$dependencies" + Write-Host " Initializr: $initializrProjectUrl" + + $initializrMetadata = Get-InitializrMetadata + if ($initializrMetadata) { + $cleanBootVersion = $bootVersion -replace '\.RELEASE$', '' + $bootVersionTrack = ($cleanBootVersion -split '\.')[0..1] -join '.' + + $latestStable = $initializrMetadata.bootVersion.values | + Where-Object { $_.id -match "^$([regex]::Escape($bootVersionTrack))\." -and + $_.id -notmatch 'SNAPSHOT|\.M\d+|\.RC\d+|\.BUILD' } | + Select-Object -First 1 + + if ($latestStable) { + $cleanLatestVersion = $latestStable.id -replace '\.RELEASE$', '' + if ($cleanLatestVersion -ne $cleanBootVersion) { + Write-Host " [BOOT UPDATE AVAILABLE] Spring Boot $cleanBootVersion -> $cleanLatestVersion" -ForegroundColor Yellow + Write-Host " To apply: Set-Content '$imageDirectory\metadata\SPRING_BOOT_VERSION' '$cleanLatestVersion'" -ForegroundColor DarkYellow + } + else { + Write-Host " Spring Boot $cleanBootVersion is current in the $bootVersionTrack track" -ForegroundColor Green + } + } + + $cleanDefaultVersion = $initializrMetadata.bootVersion.default -replace '\.RELEASE$', '' + $defaultVersionTrack = ($cleanDefaultVersion -split '\.')[0..1] -join '.' + if ($defaultVersionTrack -ne $bootVersionTrack) { + Write-Host " (Initializr default is Spring Boot $cleanDefaultVersion / $defaultVersionTrack track)" -ForegroundColor Cyan + } + } + + $temporaryDirectory = Join-Path $imagesDirectory "temp_update_$Name" + if (Test-Path $temporaryDirectory) { Remove-Item -Recurse -Force $temporaryDirectory } + New-Item -ItemType Directory -Path $temporaryDirectory | Out-Null + + try { + Push-Location $temporaryDirectory + + Write-Host "Downloading from start.spring.io..." + Invoke-WebRequest ` + -Uri "https://start.spring.io/starter.zip" ` + -Method Post ` + -Body @{ + type = "gradle-project" + bootVersion = $bootVersion + javaVersion = $javaVersion + groupId = "io.steeltoe.docker" + artifactId = $serverName + name = $applicationName + applicationName = $applicationName + description = "$applicationName for local development with Steeltoe" + language = "java" + dependencies = $dependencies + version = $imageVersion + } ` + -OutFile "$serverName.zip" + + $extractionRoot = Join-Path $temporaryDirectory "extracted" + New-Item -ItemType Directory -Path $extractionRoot | Out-Null + Expand-Archive -Path "$serverName.zip" -DestinationPath $extractionRoot -Force + + $extractedItems = Get-ChildItem -Path $extractionRoot + $extractionDirectory = if ($extractedItems.Count -eq 1 -and $extractedItems[0].PSIsContainer) { $extractedItems[0].FullName } else { $extractionRoot } + + # Compare BOM version from the freshly-generated build.gradle against the committed source. + # BOM key: springCloudVersion for config/eureka (BOM != artifact), springBootAdminVersion for SBA (BOM = artifact) + $bomVersionKey = if ($Name -eq "spring-boot-admin") { "springBootAdminVersion" } else { "springCloudVersion" } + + $generatedBuildContent = Get-Content (Join-Path $extractionDirectory "build.gradle") -Raw -ErrorAction SilentlyContinue + $committedBuildContent = Get-Content (Join-Path $imageDirectory "source" "build.gradle") -Raw -ErrorAction SilentlyContinue + + if ($generatedBuildContent) { + $bomVersionPattern = "set\('$bomVersionKey',\s*`"([^`"]+)`"\)" + $generatedBuildContent -match $bomVersionPattern | Out-Null; $generatedBomVersion = $Matches[1] + if ($committedBuildContent) { + $committedBuildContent -match $bomVersionPattern | Out-Null; $committedBomVersion = $Matches[1] + } + + if ($generatedBomVersion) { + $bomVersionChanged = $committedBomVersion -and ($committedBomVersion -ne $generatedBomVersion) + + if ($Name -eq "spring-boot-admin") { + if ($generatedBomVersion -ne $imageVersion) { + $previousVersion = $imageVersion + Set-Content (Join-Path $imageDirectory "metadata" "IMAGE_VERSION") $generatedBomVersion + Set-Content (Join-Path $imageDirectory "metadata" "IMAGE_REVISION") "" + $imageVersion = $generatedBomVersion + $versionChangeNote = if ($bomVersionChanged) { " (BOM $committedBomVersion -> $generatedBomVersion)" } else { " (was $previousVersion)" } + Write-Host " [UPDATED] IMAGE_VERSION -> $generatedBomVersion$versionChangeNote" -ForegroundColor Green + } + else { + Write-Host " $bomVersionKey`: $generatedBomVersion IMAGE_VERSION: $imageVersion (in sync)" -ForegroundColor Green + } + } + else { + # Spring Cloud release train (for example: 2025.0.2) does not equal the artifact version; + # resolve it from the Spring Cloud BOM POM on GitHub. + $bomPomPropertyName = if ($Name -eq "eureka-server") { "spring-cloud-netflix.version" } else { "spring-cloud-config.version" } + $artifactVersion = $null + try { + [xml]$bomPomDocument = Invoke-RestMethod -Uri "https://raw.githubusercontent.com/spring-cloud/spring-cloud-release/v$generatedBomVersion/spring-cloud-dependencies/pom.xml" -TimeoutSec 10 + $artifactVersion = $bomPomDocument.project.properties.$bomPomPropertyName + } + catch { } + + if ($artifactVersion) { + if ($artifactVersion -ne $imageVersion) { + $previousVersion = $imageVersion + Set-Content (Join-Path $imageDirectory "metadata" "IMAGE_VERSION") $artifactVersion + Set-Content (Join-Path $imageDirectory "metadata" "IMAGE_REVISION") "" + $imageVersion = $artifactVersion + $bomVersionNote = if ($bomVersionChanged) { " (BOM $committedBomVersion -> $generatedBomVersion)" } else { " (was $previousVersion)" } + Write-Host " [UPDATED] IMAGE_VERSION -> $artifactVersion via $bomPomPropertyName$bomVersionNote" -ForegroundColor Green + } + else { + $bomVersionNote = if ($bomVersionChanged) { " (BOM $committedBomVersion -> $generatedBomVersion)" } else { "" } + Write-Host " $bomVersionKey`: $generatedBomVersion IMAGE_VERSION: $imageVersion (in sync)$bomVersionNote" -ForegroundColor Green + } + } + else { + $statusMessage = if ($bomVersionChanged) { "[BOM CHANGED] $bomVersionKey`: $committedBomVersion -> $generatedBomVersion" } else { "$bomVersionKey`: $generatedBomVersion" } + Write-Host " $statusMessage IMAGE_VERSION: $imageVersion (BOM fetch failed)" -ForegroundColor Yellow + Write-Host " Check $bomPomPropertyName in: https://github.com/spring-cloud/spring-cloud-release/blob/v$generatedBomVersion/spring-cloud-dependencies/pom.xml" -ForegroundColor Cyan + } + } + } + } + + Push-Location $extractionDirectory + try { + if (Test-Path (Join-Path $imageDirectory "patches")) { + foreach ($patch in Get-ChildItem -Path (Join-Path $imageDirectory patches) -Filter "*.patch") { + Write-Host "Applying patch $($patch.Name)" + $patchContent = Get-Content $patch -Raw + + if (!(Get-Command "patch" -ErrorAction SilentlyContinue)) { + if (Test-Path "$Env:ProgramFiles\Git\usr\bin\patch.exe") { + $env:Path += ";$Env:ProgramFiles\Git\usr\bin" + } + else { + throw "'patch' command not found" + } + } + + $patchOutput = $patchContent | & patch -p1 2>&1 + if ($LASTEXITCODE -ne 0) { + $patchOutput | ForEach-Object { Write-Host " $_" } + throw "Patch $($patch.Name) failed" + } + } + } + } + finally { + Pop-Location + } + + $sourceDirectory = Join-Path $imageDirectory "source" + if (Test-Path $sourceDirectory) { + Remove-Item -Recurse -Force $sourceDirectory + } + + New-Item -ItemType Directory -Path $sourceDirectory -Force | Out-Null + + # Remove files we don't want committed. + # gradle-wrapper.jar is intentionally left in place, so local builds can skip the download. + # build.ps1 still validates its checksum against services.gradle.org on every build. + Get-ChildItem -Path $extractionDirectory -Recurse -Filter "*.orig" | Remove-Item -Force + foreach ($unwanted in @(".gitignore", ".gitattributes", "HELP.md")) { + $unwantedPath = Join-Path $extractionDirectory $unwanted + if (Test-Path $unwantedPath) { + Remove-Item -Force $unwantedPath + } + } + + Copy-Item -Path "$extractionDirectory\*" -Destination $sourceDirectory -Recurse -Force + + $customizationsDirectory = Join-Path $imageDirectory "customizations" + if (Test-Path $customizationsDirectory) { + # Append the image-build hardening block (builder/run-image digest pins, reproducible + # createdDate, dependency locking, test gating) to the generated build.gradle. + $buildGradleAppend = Join-Path $customizationsDirectory "build.gradle.append" + if (Test-Path $buildGradleAppend) { + Write-Host "Appending build.gradle customizations" + $buildGradlePath = Join-Path $sourceDirectory "build.gradle" + $generated = ([System.IO.File]::ReadAllText($buildGradlePath)) -replace "`r`n", "`n" + $append = ([System.IO.File]::ReadAllText($buildGradleAppend)) -replace "`r`n", "`n" + [System.IO.File]::WriteAllText($buildGradlePath, $generated.TrimEnd("`n") + "`n`n" + $append) + } + + # Overlay hand-written files on top of the generated project. + $overlayDirectory = Join-Path $customizationsDirectory "overlay" + if (Test-Path $overlayDirectory) { + Write-Host "Applying overlay files" + Copy-Item -Path (Join-Path $overlayDirectory "*") -Destination $sourceDirectory -Recurse -Force + } + } + + # git does not preserve Unix execute permissions on Windows; restore the bit so Linux CI can run gradlew + if (Test-Path (Join-Path $sourceDirectory "gradlew")) { + & git -C $imagesDirectory update-index --chmod=+x "$Name/source/gradlew" 2>&1 | Out-Null + } + + # Regenerate dependency lockfiles so they always match the freshly resolved dependencies. + # Uses the wrapper jar shipped in the generated project; requires JDK 25 and network access. + Write-Host "Regenerating dependency locks (resolving all dependencies)..." + Push-Location $sourceDirectory + try { + if ($IsLinux -or $IsMacOS) { + & chmod +x gradlew + } + $gradlewCommand = if ($IsWindows) { ".\gradlew.bat" } else { "./gradlew" } + # Pipe stdout (verbose dependency tree) to Out-Null; stderr is not redirected so that + # Gradle errors remain visible. Do not add 2>&1 here! Doing so would hide real failures. + & $gradlewCommand --no-daemon --console=plain dependencies --write-locks | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "Lockfile regeneration failed for $Name" + } + + # Drop the transient Gradle outputs from the lock run; they are not part of source. + Remove-Item -Recurse -Force (Join-Path $sourceDirectory "build") -ErrorAction Ignore + Remove-Item -Recurse -Force (Join-Path $sourceDirectory ".gradle") -ErrorAction Ignore + } + finally { + Pop-Location + } + + Write-Host "Updated source for $Name in $sourceDirectory" + } + catch { + Write-Error "Failed to update $Name : $_" + throw + } + finally { + Set-Location $imagesDirectory + if (Test-Path $temporaryDirectory) { + Start-Sleep -Milliseconds 500 + Remove-Item -Recurse -Force $temporaryDirectory + } + } +} + +# Check for updates to the Paketo Buildpack images pinned in build.gradle.append. +# All Java-based images should share these digests, so read from the first file found and report once. +# Update the builder/runImage lines in all three build.gradle.append files when an update is available. +$firstAppend = Get-ChildItem -Path $PSScriptRoot -Recurse -Filter "build.gradle.append" -ErrorAction Ignore | Select-Object -First 1 +if ($firstAppend) { + $appendContent = Get-Content $firstAppend.FullName -Raw + foreach ($check in @( + @{ Label = "builder"; Pattern = 'builder\s*=\s*"([^@"]+)@(sha256:[a-f0-9]+)"' }, + @{ Label = "runImage"; Pattern = 'runImage\s*=\s*"([^@"]+)@(sha256:[a-f0-9]+)"' } + )) { + if ($appendContent -match $check.Pattern) { + $imageName = $Matches[1] + $pinnedDigest = $Matches[2] + $latestDigest = Get-BuildpackImageDigest $imageName + if ($latestDigest) { + if ($latestDigest -ne $pinnedDigest) { + Write-Host "[UPDATE AVAILABLE] $($check.Label): $imageName" -ForegroundColor Yellow + Write-Host " $pinnedDigest -> $latestDigest" -ForegroundColor Yellow + Write-Host " Update $($check.Label) in all build.gradle.append files" -ForegroundColor DarkYellow + } + else { + Write-Host "$($check.Label) digest is current ($pinnedDigest)" -ForegroundColor Green + } + } + else { + Write-Host "(Could not check Docker Hub for $imageName)" -ForegroundColor DarkGray + } + } + } + Write-Host "" + Write-Host ('=' * 60) -ForegroundColor DarkGray + Write-Host "" +} + +# Wrap in @() so a single -Names value stays an array (prevent iterating on the characters in the name). +$imageNames = @(if ($Names) { $Names } else { @("config-server", "eureka-server", "spring-boot-admin") }) + +for ($index = 0; $index -lt $imageNames.Count; $index++) { + Update-Project -Name $imageNames[$index] + if ($index -lt $imageNames.Count - 1) { + Write-Host "" + Write-Host ('=' * 60) -ForegroundColor DarkGray + Write-Host "" + } +}