From 28fae7fbdde0fc7f95e76e5cf66b4376fa529839 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 27 Jan 2026 17:04:50 -0600 Subject: [PATCH 01/11] bump to Spring Boot 3.5.10, update READMEs, add AGENTS.md - Log dependencies requested in calls to start.spring.io - Enhance patch handling to catch more failure scenarios - Use eol=lf for .patch files - fix enableconfigserver.patch --- .gitattributes | 1 + AGENTS.md | 57 +++++++++++++++++++ build.ps1 | 38 +++++++++++-- config-server/README.md | 8 +-- config-server/metadata/IMAGE_REVISION | 2 +- config-server/metadata/IMAGE_VERSION | 2 +- config-server/metadata/SPRING_BOOT_VERSION | 2 +- .../patches/enableconfigserver.patch | 3 +- eureka-server/README.md | 2 +- eureka-server/metadata/IMAGE_REVISION | 1 + eureka-server/metadata/SPRING_BOOT_VERSION | 2 +- spring-boot-admin/README.md | 2 +- spring-boot-admin/metadata/IMAGE_VERSION | 2 +- .../metadata/SPRING_BOOT_VERSION | 2 +- uaa-server/README.md | 12 ++-- 15 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 AGENTS.md diff --git a/.gitattributes b/.gitattributes index 6c074fe..2d80dd0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ *.jar binary * text eol=lf +*.patch text eol=lf diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2034656 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,57 @@ +# Agent Instructions and Reminders + +This file contains important reminders and guidelines for AI agents working on this codebase. + +## Build Script + +### Avoid `-DisableCache` Flag + +**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: + +1. Delete the expanded project folder (e.g., `workspace/springbootadmin/`) +2. Run `.\build.ps1 ` without the flag + +### Testing Changes + +Before submitting patch changes: + +1. Run a dry-run of each patch: `Get-Content patch.patch | & patch --dry-run -p1` +2. If dry-run succeeds, run the full build and verify Java compilation +3. Test the resulting Docker image with a real client app + +## Patch Files + +**CRITICAL**: When modifying patch files (`.patch` files in `patches/` directories), you MUST update the line numbers in the hunk headers when adding or removing lines. + +### Patch Format Rules + +1. **Hunk headers must be accurate**: The format is `@@ -old_start,old_count +new_start,new_count @@` + - `new_count` must equal the exact number of lines with `+` prefix in the hunk + - For new file patches (`--- /dev/null`), `old_count` is 0 +2. **Trailing newlines are required**: Patch files must end with a newline character. The `patch` utility will fail with "unexpected end of file" otherwise. +3. **Preserve exact whitespace**: Context lines must match the target file exactly, including trailing spaces and tabs. +4. **New file patches**: Use `/dev/null` as the old file: + + ```diff + --- /dev/null + +++ ./path/to/NewFile.java 2026-01-27 00:00:00.000000000 +0000 + @@ -0,0 +1,N @@ + +line 1 + +line 2 + ... + ``` + +### Example + +If a patch adds 1 line, the hunk header must reflect this: + +```diff +-@@ -37,3 +37,10 @@ ++@@ -37,3 +37,11 @@ +``` + +### Why This Matters + +Incorrect line numbers cause patch application to fail, breaking the build process. diff --git a/build.ps1 b/build.ps1 index 9182d20..715e469 100755 --- a/build.ps1 +++ b/build.ps1 @@ -207,7 +207,7 @@ try { # Scaffold project on start.spring.io if (!(Test-Path "$artifactName")) { - Write-Host "Using start.spring.io to create project" + Write-Host "Using start.spring.io to create project with dependencies: $dependencies" Invoke-WebRequest ` -Uri "https://start.spring.io/starter.zip" ` -Method Post ` @@ -238,9 +238,39 @@ try { # Apply patches foreach ($patch in Get-ChildItem -Path (Join-Path $ImageDirectory patches) -Filter "*.patch") { Write-Host "Applying patch $($patch.Name)" - Get-Content $patch | & patch -p1 - if ($LASTEXITCODE -ne 0) { - throw "Patch failed with exit code $LASTEXITCODE" + $patchContent = Get-Content $patch -Raw + $patchContent | & patch -p1 + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + # Check if this is a "new file" patch (old file is /dev/null) + # New file patches may return non-zero exit codes but still succeed + $isNewFilePatch = $patchContent -match '(?m)^--- /dev/null' + if ($isNewFilePatch) { + # Verify the file was actually created by checking if target exists + # Extract the target file path from the +++ line (first one after --- /dev/null) + $targetFile = $null + if ($patchContent -match '(?m)^--- /dev/null\s+.*\r?\n\+\+\+ ([^\t]+)') { + $targetFile = $matches[1] -replace '^\./', '' + # Remove any trailing whitespace or timestamp + $targetFile = $targetFile.Trim() + if ($targetFile -and (Test-Path $targetFile)) { + Write-Host "Patch $($patch.Name) created new file successfully: $targetFile" + } else { + Write-Host "Warning: Patch $($patch.Name) appears to be a new file patch but target not found: $targetFile" + throw "Patch $($patch.Name) failed with exit code $exitCode" + } + } else { + Write-Host "Warning: Patch $($patch.Name) appears to be a new file patch but could not determine target file path" + throw "Patch $($patch.Name) failed with exit code $exitCode" + } + } else { + Write-Host "Error: Patch $($patch.Name) failed with exit code $exitCode" + Write-Host "Patch content preview:" + Get-Content $patch | Select-Object -First 10 | ForEach-Object { Write-Host " $_" } + throw "Patch $($patch.Name) failed with exit code $exitCode" + } + } else { + Write-Host "Patch $($patch.Name) applied successfully" } } diff --git a/config-server/README.md b/config-server/README.md index 0c2107d..ffbb8b7 100644 --- a/config-server/README.md +++ b/config-server/README.md @@ -7,20 +7,20 @@ Image for SteeltoeOSS local development with [Spring Cloud Config Server](https: Default configuration: ```shell -docker run --publish 8888:8888 steeltoe.azurecr.io/config-server +docker run --publish 8888:8888 steeltoe.azurecr.io/config-server:4 ``` Custom git repo configuration: ```shell -docker run --publish 8888:8888 steeltoe.azurecr.io/config-server \ +docker run --publish 8888:8888 steeltoe.azurecr.io/config-server:4 \ --spring.cloud.config.server.git.uri=https://github.com/myorg/myrepo.git ``` Local file system configuration: ```shell -docker run --publish 8888:8888 --volume /path/to/my/config:/config steeltoe.azurecr.io/config-server \ +docker run --publish 8888:8888 --volume /path/to/my/config:/config steeltoe.azurecr.io/config-server:4 \ --spring.profiles.active=native \ --spring.cloud.config.server.native.searchLocations=file:///config ``` @@ -28,7 +28,7 @@ docker run --publish 8888:8888 --volume /path/to/my/config:/config steeltoe.azur With basic auth: ```shell -docker run --publish 8888:8888 steeltoe.azurecr.io/config-server \ +docker run --publish 8888:8888 steeltoe.azurecr.io/config-server:4 \ --auth.enabled=true \ --auth.username=myCustomUser \ --auth.password=myCustomPassword diff --git a/config-server/metadata/IMAGE_REVISION b/config-server/metadata/IMAGE_REVISION index d00491f..8b13789 100644 --- a/config-server/metadata/IMAGE_REVISION +++ b/config-server/metadata/IMAGE_REVISION @@ -1 +1 @@ -1 + diff --git a/config-server/metadata/IMAGE_VERSION b/config-server/metadata/IMAGE_VERSION index 8089590..f77856a 100644 --- a/config-server/metadata/IMAGE_VERSION +++ b/config-server/metadata/IMAGE_VERSION @@ -1 +1 @@ -4.3.0 +4.3.1 diff --git a/config-server/metadata/SPRING_BOOT_VERSION b/config-server/metadata/SPRING_BOOT_VERSION index c492825..efe3085 100644 --- a/config-server/metadata/SPRING_BOOT_VERSION +++ b/config-server/metadata/SPRING_BOOT_VERSION @@ -1 +1 @@ -3.5.6 +3.5.10 diff --git a/config-server/patches/enableconfigserver.patch b/config-server/patches/enableconfigserver.patch index c971079..e946478 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 @@ +@@ -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/eureka-server/README.md b/eureka-server/README.md index c81667c..26772fb 100644 --- a/eureka-server/README.md +++ b/eureka-server/README.md @@ -5,7 +5,7 @@ Image for SteeltoeOSS local development with [Spring Cloud Eureka Server](https: ## Running ```shell -docker run --rm -it --pull=always -p 8761:8761 --name steeltoe-eureka steeltoe.azurecr.io/eureka-server +docker run --rm -it --pull=always -p 8761:8761 --name steeltoe-eureka steeltoe.azurecr.io/eureka-server:4 ``` ## Resources diff --git a/eureka-server/metadata/IMAGE_REVISION b/eureka-server/metadata/IMAGE_REVISION index e69de29..d00491f 100644 --- a/eureka-server/metadata/IMAGE_REVISION +++ b/eureka-server/metadata/IMAGE_REVISION @@ -0,0 +1 @@ +1 diff --git a/eureka-server/metadata/SPRING_BOOT_VERSION b/eureka-server/metadata/SPRING_BOOT_VERSION index c492825..efe3085 100644 --- a/eureka-server/metadata/SPRING_BOOT_VERSION +++ b/eureka-server/metadata/SPRING_BOOT_VERSION @@ -1 +1 @@ -3.5.6 +3.5.10 diff --git a/spring-boot-admin/README.md b/spring-boot-admin/README.md index 4bebf29..971e83d 100644 --- a/spring-boot-admin/README.md +++ b/spring-boot-admin/README.md @@ -5,5 +5,5 @@ Image for SteeltoeOSS local development with Date: Tue, 27 Jan 2026 17:11:16 -0600 Subject: [PATCH 02/11] Support trusting Aspire dev certs - add custom deserialization for aot/native-compiled SBA to avoid breakage over 'type' property in Steeltoe responses fixup metadata skip self-replication in Eureka --- build.ps1 | 22 +++ .../patches/application.properties.patch | 5 +- .../patches/enableconfigserver.patch | 2 +- .../patches/application.properties.patch | 11 +- shared/ssl-config/SslTrustConfiguration.java | 186 ++++++++++++++++++ spring-boot-admin/metadata/IMAGE_VERSION | 2 +- .../patches/application.properties.patch | 5 +- spring-boot-admin/patches/build.gradle.patch | 5 +- .../patches/enable-springbootadmin.patch | 15 +- .../spring-boot-admin-ssl-config.patch | 82 ++++++++ .../patches/steeltoe-admin-config.patch | 129 ++++++++++++ 11 files changed, 451 insertions(+), 13 deletions(-) create mode 100644 shared/ssl-config/SslTrustConfiguration.java create mode 100644 spring-boot-admin/patches/spring-boot-admin-ssl-config.patch create mode 100644 spring-boot-admin/patches/steeltoe-admin-config.patch diff --git a/build.ps1 b/build.ps1 index 715e469..afd7f77 100755 --- a/build.ps1 +++ b/build.ps1 @@ -274,6 +274,28 @@ try { } } + # Copy shared SSL configuration files + # Get repository root (parent of workspace directory) + # We're currently in workspace/$serverName, so go up two levels to get to repo root + $currentDir = (Get-Location).Path + $workspaceDir = Split-Path -Parent $currentDir + $repoRoot = Split-Path -Parent $workspaceDir + $sharedSslDir = Join-Path $repoRoot "shared" "ssl-config" + if (Test-Path $sharedSslDir) { + $sharedJavaDir = Join-Path "src" "main" "java" "io" "steeltoe" "docker" "ssl" + if (!(Test-Path $sharedJavaDir)) { + New-Item -ItemType Directory -Path $sharedJavaDir -Force | Out-Null + } + + # Copy SslTrustConfiguration.java (used by all services) + $sslTrustConfig = Join-Path $sharedSslDir "SslTrustConfiguration.java" + if (Test-Path $sslTrustConfig) { + $targetFile = Join-Path $sharedJavaDir "SslTrustConfiguration.java" + Write-Host "Copying shared SSL trust configuration" + Copy-Item $sslTrustConfig $targetFile -Force + } + } + # Build the image $gradleArgs = @("bootBuildImage", "--imageName=$ImageNameWithTag") if ($env:GITHUB_ACTIONS -eq "true") { diff --git a/config-server/patches/application.properties.patch b/config-server/patches/application.properties.patch index cda4bd4..7b35e64 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.ssl=INFO diff --git a/config-server/patches/enableconfigserver.patch b/config-server/patches/enableconfigserver.patch index e946478..7ffc6be 100644 --- a/config-server/patches/enableconfigserver.patch +++ b/config-server/patches/enableconfigserver.patch @@ -1,5 +1,5 @@ --- 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 ++++ 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; diff --git a/eureka-server/patches/application.properties.patch b/eureka-server/patches/application.properties.patch index b48a685..4367416 100644 --- a/eureka-server/patches/application.properties.patch +++ b/eureka-server/patches/application.properties.patch @@ -1,12 +1,19 @@ --- eurekaserver/src/main/resources/application.properties 2024-02-21 15:43:09.000000000 -0600 -+++ eurekaserver/src/main/resources/application.properties 2024-04-02 13:15:18.461432100 -0500 -@@ -0,0 +1,9 @@ ++++ eurekaserver/src/main/resources/application.properties 2026-01-27 00:00:00.000000000 -0500 +@@ -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.enableSelfPreservation = 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.ssl = INFO +spring.main.lazy-initialization = true diff --git a/shared/ssl-config/SslTrustConfiguration.java b/shared/ssl-config/SslTrustConfiguration.java new file mode 100644 index 0000000..73b1e53 --- /dev/null +++ b/shared/ssl-config/SslTrustConfiguration.java @@ -0,0 +1,186 @@ +package io.steeltoe.docker.ssl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import javax.security.auth.x500.X500Principal; + +/** + * SSL Trust Configuration + * + * This configuration class provides SSL certificate trust support for development environments. + * It automatically loads certificates from environment variables or standard locations and + * creates a custom TrustManager that trusts both standard CA certificates and development + * certificates (e.g., Aspire development certificates). + * + *

Certificate locations checked (in order): + *

    + *
  • {@code SSL_CERT_DIR} environment variable (directory containing certificates)
  • + *
  • {@code /usr/lib/ssl/aspire} (default development certificate directory, e.g., for Aspire)
  • + *
  • {@code SSL_CERT_FILE} environment variable (single certificate file)
  • + *
+ * + *

Supported certificate formats: .pem, .crt, .cer + * + *

Example usage with Aspire: + *

+ * // Aspire automatically sets SSL_CERT_DIR or mounts certificates at /usr/lib/ssl/aspire
+ * // This configuration will automatically detect and trust those certificates
+ * 
+ */ +@Configuration +public class SslTrustConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(SslTrustConfiguration.class); + + @Bean + public X509TrustManager sslTrustManager() { + try { + TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + defaultTmf.init((KeyStore) null); + javax.net.ssl.TrustManager[] trustManagers = defaultTmf.getTrustManagers(); + if (trustManagers == null || trustManagers.length == 0 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("No X509TrustManager available from default TrustManagerFactory"); + } + X509TrustManager defaultTrustManager = (X509TrustManager) trustManagers[0]; + + List devCerts = loadDevelopmentCertificates(); + if (devCerts.isEmpty()) { + logger.info("SSL trust: Using default trust manager (no development certificates found)"); + return defaultTrustManager; + } + + logger.info("SSL trust: Loaded {} development certificate(s)", devCerts.size()); + return new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + defaultTrustManager.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + defaultTrustManager.checkServerTrusted(chain, authType); + } catch (CertificateException e) { + // If default validation fails, check if any certificate in the chain + // is signed by or matches a development certificate + logger.debug("Default trust validation failed, checking development certificates..."); + for (X509Certificate cert : chain) { + X500Principal certSubject = cert.getSubjectX500Principal(); + X500Principal certIssuer = cert.getIssuerX500Principal(); + logger.trace("Checking certificate: {}", certSubject); + + // Check if this certificate matches or is signed by a dev cert + for (X509Certificate devCert : devCerts) { + X500Principal devCertSubject = devCert.getSubjectX500Principal(); + X500Principal devCertIssuer = devCert.getIssuerX500Principal(); + + // Check if certificate matches dev cert (same serial/issuer or exact match) + if (cert.getSerialNumber().equals(devCert.getSerialNumber()) || + certIssuer.equals(devCertIssuer) || + cert.equals(devCert)) { + logger.debug("Trusting certificate signed by development CA: {}", certSubject); + return; // Trusted by development CA + } + + // Check if this certificate's issuer matches a dev cert's subject + // (meaning the dev cert is the CA that signed this cert) + if (certIssuer.equals(devCertSubject)) { + logger.debug("Trusting certificate signed by development CA: {}", certSubject); + return; // Trusted by development CA + } + } + } + // If we get here, the certificate chain doesn't include any development certificates + logger.warn("Certificate validation failed and no development certificate found in chain"); + throw e; + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate[] defaultCerts = defaultTrustManager.getAcceptedIssuers(); + X509Certificate[] allCerts = new X509Certificate[defaultCerts.length + devCerts.size()]; + System.arraycopy(defaultCerts, 0, allCerts, 0, defaultCerts.length); + System.arraycopy(devCerts.toArray(new X509Certificate[0]), 0, allCerts, defaultCerts.length, devCerts.size()); + return allCerts; + } + }; + } catch (Exception e) { + logger.error("Failed to create SSL trust manager, using default", e); + try { + TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + defaultTmf.init((KeyStore) null); + javax.net.ssl.TrustManager[] trustManagers = defaultTmf.getTrustManagers(); + if (trustManagers == null || trustManagers.length == 0 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("No X509TrustManager available from default TrustManagerFactory"); + } + return (X509TrustManager) trustManagers[0]; + } catch (Exception ex) { + logger.error("Failed to create default trust manager", ex); + throw new RuntimeException("Failed to create trust manager", ex); + } + } + } + + private List loadDevelopmentCertificates() { + List certificates = new ArrayList<>(); + logger.debug("Loading development certificates..."); + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + String[] certPaths = { + System.getenv("SSL_CERT_DIR"), + "/usr/lib/ssl/aspire", + System.getenv("SSL_CERT_FILE") + }; + + logger.trace("Checking certificate paths: SSL_CERT_DIR={}, /usr/lib/ssl/aspire, SSL_CERT_FILE={}", + System.getenv("SSL_CERT_DIR"), System.getenv("SSL_CERT_FILE")); + + for (String certPath : certPaths) { + if (certPath == null) continue; + Path path = Paths.get(certPath); + if (Files.isDirectory(path)) { + logger.debug("Scanning directory for certificates: {}", certPath); + try (var stream = Files.walk(path)) { + stream.filter(Files::isRegularFile) + .filter(p -> p.toString().matches(".*\\.(pem|crt|cer)$")) + .forEach(p -> { + try { + try (var inputStream = Files.newInputStream(p)) { + certificates.add((X509Certificate) certFactory.generateCertificate(inputStream)); + logger.debug("Loaded certificate: {}", p); + } + } catch (Exception e) { + logger.warn("Failed to load certificate {}: {}", p, e.getMessage()); + } + }); + } + } else if (Files.isRegularFile(path)) { + try (var inputStream = Files.newInputStream(path)) { + certificates.add((X509Certificate) certFactory.generateCertificate(inputStream)); + logger.debug("Loaded certificate: {}", path); + } catch (Exception e) { + logger.warn("Failed to load certificate {}: {}", path, e.getMessage()); + } + } + } + } catch (Exception e) { + logger.error("Error loading development certificates", e); + } + return certificates; + } +} diff --git a/spring-boot-admin/metadata/IMAGE_VERSION b/spring-boot-admin/metadata/IMAGE_VERSION index c492825..3cf5751 100644 --- a/spring-boot-admin/metadata/IMAGE_VERSION +++ b/spring-boot-admin/metadata/IMAGE_VERSION @@ -1 +1 @@ -3.5.6 +3.5.7 diff --git a/spring-boot-admin/patches/application.properties.patch b/spring-boot-admin/patches/application.properties.patch index 4963c92..82ef4d5 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.ssl=INFO diff --git a/spring-boot-admin/patches/build.gradle.patch b/spring-boot-admin/patches/build.gradle.patch index afc78c3..3b19e03 100644 --- a/spring-boot-admin/patches/build.gradle.patch +++ b/spring-boot-admin/patches/build.gradle.patch @@ -1,6 +1,6 @@ --- ./build.gradle 2025-09-22 14:48:20.000000000 -0500 +++ ./build.gradle 2026-01-27 00:00:00.000000000 -0500 -@@ -38,3 +38,10 @@ +@@ -38,3 +38,11 @@ tasks.named('test') { useJUnitPlatform() } @@ -8,6 +8,7 @@ +bootBuildImage { + createdDate = "now" + environment = [ -+ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true" ++ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", ++ "BP_NATIVE_IMAGE_BUILD_ARGUMENTS": "-H:+UnlockExperimentalVMOptions" + ] +} diff --git a/spring-boot-admin/patches/enable-springbootadmin.patch b/spring-boot-admin/patches/enable-springbootadmin.patch index ebd9f14..877a062 100644 --- a/spring-boot-admin/patches/enable-springbootadmin.patch +++ b/spring-boot-admin/patches/enable-springbootadmin.patch @@ -1,13 +1,22 @@ --- ./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 2024-09-20 12:49:59.410273961 -0500 -@@ -2,8 +2,10 @@ ++++ ./src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdmin.java 2026-01-27 00:00:00.000000000 -0500 +@@ -1,13 +1,18 @@ + package io.steeltoe.docker.springbootadmin; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import de.codecentric.boot.admin.server.config.EnableAdminServer; ++import org.springframework.context.annotation.ComponentScan; ++import org.springframework.context.annotation.Import; - @SpringBootApplication +@EnableAdminServer ++@ComponentScan(basePackages = { "io.steeltoe.docker.springbootadmin", "io.steeltoe.docker.ssl" }) ++@Import(SteeltoeAdminConfiguration.class) + @SpringBootApplication public class SpringBootAdmin { public static void main(String[] args) { + SpringApplication.run(SpringBootAdmin.class, args); + } +- + } diff --git a/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch b/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch new file mode 100644 index 0000000..2aec452 --- /dev/null +++ b/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch @@ -0,0 +1,82 @@ +--- /dev/null ++++ ./src/main/java/io/steeltoe/docker/ssl/SpringBootAdminSslConfiguration.java 2026-01-27 00:00:00.000000000 +0000 +@@ -0,0 +1,79 @@ ++package io.steeltoe.docker.ssl; ++ ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.springframework.beans.factory.ObjectProvider; ++import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; ++import org.springframework.context.annotation.Bean; ++import org.springframework.context.annotation.Configuration; ++import org.springframework.http.client.reactive.ClientHttpConnector; ++import org.springframework.http.client.reactive.ReactorClientHttpConnector; ++import io.netty.handler.ssl.SslContext; ++import reactor.netty.http.client.HttpClient; ++import reactor.netty.tcp.SslProvider; ++import reactor.netty.tcp.TcpSslContextSpec; ++ ++import javax.net.ssl.X509TrustManager; ++ ++/** ++ * Spring Boot Admin SSL Configuration ++ * ++ * Configures Spring Boot Admin's WebClient to use the shared SSL trust manager ++ * for trusting development certificates (e.g., Aspire development certificates). ++ * ++ * Uses ObjectProvider for AOT compatibility - the trust manager is resolved at runtime ++ * when the bean method is called, not at configuration class construction time. ++ */ ++@Configuration ++public class SpringBootAdminSslConfiguration { ++ ++ private static final Logger logger = LoggerFactory.getLogger(SpringBootAdminSslConfiguration.class); ++ private final ObjectProvider trustManagerProvider; ++ ++ public SpringBootAdminSslConfiguration(ObjectProvider trustManagerProvider) { ++ this.trustManagerProvider = trustManagerProvider; ++ } ++ ++ /** ++ * Provides a ClientHttpConnector with custom SSL trust for Spring Boot Admin's WebClient. ++ * ++ * Uses ObjectProvider to defer trust manager resolution until runtime, making this ++ * AOT-compatible. The trust manager is resolved when this bean method is called, ++ * not during AOT processing at build time. ++ */ ++ @Bean ++ @ConditionalOnMissingBean(ClientHttpConnector.class) ++ public ClientHttpConnector clientHttpConnector() { ++ logger.info("Configuring Spring Boot Admin WebClient with SSL trust support"); ++ X509TrustManager trustManager = trustManagerProvider.getIfAvailable(); ++ ++ if (trustManager == null) { ++ logger.debug("No custom X509TrustManager available, using default SSL configuration"); ++ return new ReactorClientHttpConnector(HttpClient.create()); ++ } ++ ++ try { ++ logger.info("Using custom X509TrustManager for Spring Boot Admin WebClient"); ++ // Build SslContext first to avoid deprecated sslContext(ProtocolSslContextSpec) method ++ SslContext sslContext = TcpSslContextSpec.forClient() ++ .configure(sslContextBuilder -> { ++ sslContextBuilder.trustManager(trustManager); ++ }) ++ .sslContext(); ++ ++ SslProvider sslProvider = SslProvider.builder() ++ .sslContext(sslContext) ++ .build(); ++ ++ HttpClient httpClient = HttpClient.create() ++ .secure(sslProvider); ++ ++ logger.debug("Configured Spring Boot Admin WebClient with custom SSL trust"); ++ return new ReactorClientHttpConnector(httpClient); ++ } catch (Exception e) { ++ logger.error("Failed to configure SSL trust for Spring Boot Admin WebClient, using default", e); ++ // Fall back to default connector if SSL configuration fails ++ return new ReactorClientHttpConnector(HttpClient.create()); ++ } ++ } ++} diff --git a/spring-boot-admin/patches/steeltoe-admin-config.patch b/spring-boot-admin/patches/steeltoe-admin-config.patch new file mode 100644 index 0000000..783b16c --- /dev/null +++ b/spring-boot-admin/patches/steeltoe-admin-config.patch @@ -0,0 +1,129 @@ +--- /dev/null ++++ ./src/main/java/io/steeltoe/docker/springbootadmin/SteeltoeAdminConfiguration.java 2026-01-27 00:00:00.000000000 +0000 +@@ -0,0 +1,126 @@ ++package io.steeltoe.docker.springbootadmin; ++ ++import com.fasterxml.jackson.core.JsonParser; ++import com.fasterxml.jackson.databind.DeserializationContext; ++import com.fasterxml.jackson.databind.JsonDeserializer; ++import com.fasterxml.jackson.databind.JsonNode; ++import com.fasterxml.jackson.databind.ObjectMapper; ++import com.fasterxml.jackson.databind.module.SimpleModule; ++import de.codecentric.boot.admin.server.web.client.InstanceWebClientCustomizer; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.springframework.beans.factory.ObjectProvider; ++import org.springframework.context.annotation.Bean; ++import org.springframework.context.annotation.Configuration; ++import org.springframework.core.annotation.Order; ++import org.springframework.http.client.reactive.ClientHttpConnector; ++import org.springframework.http.codec.json.Jackson2JsonDecoder; ++import org.springframework.http.codec.json.Jackson2JsonEncoder; ++import org.springframework.web.reactive.function.client.ExchangeStrategies; ++import org.springframework.web.reactive.function.client.WebClient; ++ ++import java.io.IOException; ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * Configuration to make Spring Boot Admin compatible with Steeltoe actuator responses. ++ * ++ * Steeltoe adds a "type":"Steeltoe" property to its actuator index response, which causes ++ * deserialization failures in AOT-compiled Spring Boot Admin. This configuration provides ++ * a custom WebClient with a manual deserializer that only extracts the _links field. ++ * ++ * @see GraalVM Reflection Metadata ++ */ ++@Configuration(proxyBeanMethods = false) ++public class SteeltoeAdminConfiguration { ++ ++ private static final Logger log = LoggerFactory.getLogger(SteeltoeAdminConfiguration.class); ++ ++ @Bean ++ @Order(-100) ++ public InstanceWebClientCustomizer steeltoeInstanceWebClientCustomizer( ++ ObjectMapper objectMapper, ++ ObjectProvider clientHttpConnectorProvider) { ++ ++ log.info("Configuring Spring Boot Admin WebClient for Steeltoe compatibility"); ++ ++ return (builder) -> { ++ try { ++ Class responseClass = Class.forName( ++ "de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy$Response"); ++ Class endpointRefClass = Class.forName( ++ "de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy$Response$EndpointRef"); ++ ++ // Copy the base ObjectMapper to preserve other configuration (date formats, etc.) ++ // while adding our custom deserializer for the actuator index response ++ ObjectMapper mapper = objectMapper.copy(); ++ SimpleModule module = new SimpleModule("SteeltoeCompatibility"); ++ @SuppressWarnings({"unchecked", "rawtypes"}) ++ JsonDeserializer deserializer = new ActuatorIndexResponseDeserializer(responseClass, endpointRefClass); ++ module.addDeserializer((Class) responseClass, deserializer); ++ mapper.registerModule(module); ++ ++ ExchangeStrategies strategies = ExchangeStrategies.builder() ++ .codecs(configurer -> { ++ configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper)); ++ configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper)); ++ }) ++ .build(); ++ ++ WebClient.Builder webClientBuilder = WebClient.builder().exchangeStrategies(strategies); ++ clientHttpConnectorProvider.ifAvailable(connector -> { ++ webClientBuilder.clientConnector(connector); ++ log.info("Using custom ClientHttpConnector for SSL trust"); ++ }); ++ ++ builder.webClient(webClientBuilder); ++ log.info("Steeltoe-compatible WebClient configuration complete"); ++ ++ } catch (ClassNotFoundException e) { ++ log.error("Failed to load SBA Response classes: {}", e.getMessage()); ++ } ++ }; ++ } ++ ++ @SuppressWarnings("rawtypes") ++ public static class ActuatorIndexResponseDeserializer extends JsonDeserializer { ++ private static final Logger log = LoggerFactory.getLogger(ActuatorIndexResponseDeserializer.class); ++ private final Class responseClass; ++ private final Class endpointRefClass; ++ ++ public ActuatorIndexResponseDeserializer(Class responseClass, Class endpointRefClass) { ++ this.responseClass = responseClass; ++ this.endpointRefClass = endpointRefClass; ++ } ++ ++ @Override ++ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { ++ JsonNode node = p.getCodec().readTree(p); ++ try { ++ Object response = responseClass.getDeclaredConstructor().newInstance(); ++ Map links = new HashMap<>(); ++ if (node.has("_links")) { ++ node.get("_links").fields().forEachRemaining(entry -> { ++ try { ++ JsonNode linkNode = entry.getValue(); ++ String href = linkNode.has("href") ? linkNode.get("href").asText() : null; ++ boolean templated = linkNode.has("templated") && linkNode.get("templated").asBoolean(); ++ java.lang.reflect.Constructor ctor = endpointRefClass.getDeclaredConstructor(String.class, boolean.class); ++ ctor.setAccessible(true); ++ Object ref = ctor.newInstance(href, templated); ++ links.put(entry.getKey(), ref); ++ } catch (Exception e) { ++ log.warn("Failed to parse link '{}': {}", entry.getKey(), e.getMessage()); ++ } ++ }); ++ } ++ responseClass.getMethod("setLinks", Map.class).invoke(response, links); ++ log.debug("Deserialized {} endpoint links", links.size()); ++ return response; ++ } catch (Exception e) { ++ throw new IOException("Failed to deserialize actuator index response", e); ++ } ++ } ++ } ++} From 5ce7172898fa9d89d3154c09b10c209020c8a2f4 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 28 Jan 2026 13:14:59 -0600 Subject: [PATCH 03/11] Apply suggestions from code review (#53) * Update eureka-server/patches/application.properties.patch * Fix line count in patch * Update build.ps1 to trim/join IMAGE_REVISION and check for non-empty * Use cryptographic signature verification in SslTrustConfiguration --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TimHess <3947063+TimHess@users.noreply.github.com> --- build.ps1 | 40 ++++++++++++++----- .../patches/application.properties.patch | 3 +- shared/ssl-config/SslTrustConfiguration.java | 33 +++++++++------ 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/build.ps1 b/build.ps1 index afd7f77..5b0bbb1 100755 --- a/build.ps1 +++ b/build.ps1 @@ -112,11 +112,11 @@ try { if (!$Tag) { if ($env:GITHUB_ACTIONS -eq "true") { $ImageNameWithTag = "$DockerOrg/${Name}:$Version" - $Revision = Get-Content (Join-Path $ImageDirectory "metadata" "IMAGE_REVISION") - if ($Revision) { + $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") | ForEach-Object { $_.replace("$Name","$DockerOrg/$Name") })" + $AdditionalTags = "$(Get-Content (Join-Path $ImageDirectory "metadata" "ADDITIONAL_TAGS") -ErrorAction SilentlyContinue | ForEach-Object { $_.replace("$Name","$DockerOrg/$Name") })" } else { $ImageNameWithTag = "$DockerOrg/${Name}:dev" @@ -249,8 +249,21 @@ try { # Verify the file was actually created by checking if target exists # Extract the target file path from the +++ line (first one after --- /dev/null) $targetFile = $null - if ($patchContent -match '(?m)^--- /dev/null\s+.*\r?\n\+\+\+ ([^\t]+)') { - $targetFile = $matches[1] -replace '^\./', '' + $lines = $patchContent -split "`n" + $foundDevNull = $false + foreach ($line in $lines) { + if (-not $foundDevNull) { + if ($line -match '^--- /dev/null') { + $foundDevNull = $true + } + continue + } + if ($line -match '^\+\+\+\s+([^\t\r\n]+)') { + $targetFile = $matches[1] -replace '^\./', '' + break + } + } + if ($targetFile) { # Remove any trailing whitespace or timestamp $targetFile = $targetFile.Trim() if ($targetFile -and (Test-Path $targetFile)) { @@ -282,17 +295,24 @@ try { $repoRoot = Split-Path -Parent $workspaceDir $sharedSslDir = Join-Path $repoRoot "shared" "ssl-config" if (Test-Path $sharedSslDir) { - $sharedJavaDir = Join-Path "src" "main" "java" "io" "steeltoe" "docker" "ssl" - if (!(Test-Path $sharedJavaDir)) { - New-Item -ItemType Directory -Path $sharedJavaDir -Force | Out-Null + # Place the shared SSL configuration into the app's base package + # so that it is discovered by Spring's component scanning. + $appJavaDir = Join-Path "src" "main" "java" "io" "steeltoe" "docker" $serverName + if (!(Test-Path $appJavaDir)) { + New-Item -ItemType Directory -Path $appJavaDir -Force | Out-Null } # Copy SslTrustConfiguration.java (used by all services) $sslTrustConfig = Join-Path $sharedSslDir "SslTrustConfiguration.java" if (Test-Path $sslTrustConfig) { - $targetFile = Join-Path $sharedJavaDir "SslTrustConfiguration.java" - Write-Host "Copying shared SSL trust configuration" + $targetFile = Join-Path $appJavaDir "SslTrustConfiguration.java" + Write-Host "Copying shared SSL trust configuration into app package" Copy-Item $sslTrustConfig $targetFile -Force + + # Update the package declaration so the class resides in the app's package + $packagePattern = 'package\s+io\.steeltoe\.docker\.ssl;' + $newPackageDeclaration = "package io.steeltoe.docker.$serverName;" + (Get-Content $targetFile) -replace $packagePattern, $newPackageDeclaration | Set-Content $targetFile } } diff --git a/eureka-server/patches/application.properties.patch b/eureka-server/patches/application.properties.patch index 4367416..77978de 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,16 @@ +@@ -0,0 +1,15 @@ +server.port = 8761 +eureka.client.fetch-registry = false +eureka.client.register-with-eureka = false @@ -9,7 +9,6 @@ +# 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.enableSelfPreservation = false +eureka.server.numberOfReplicationRetries = 0 +eureka.server.evictionIntervalTimerInMs = 1000 +eureka.server.responseCacheUpdateIntervalMs = 1000 diff --git a/shared/ssl-config/SslTrustConfiguration.java b/shared/ssl-config/SslTrustConfiguration.java index 73b1e53..71c842f 100644 --- a/shared/ssl-config/SslTrustConfiguration.java +++ b/shared/ssl-config/SslTrustConfiguration.java @@ -80,27 +80,34 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) throws logger.debug("Default trust validation failed, checking development certificates..."); for (X509Certificate cert : chain) { X500Principal certSubject = cert.getSubjectX500Principal(); - X500Principal certIssuer = cert.getIssuerX500Principal(); logger.trace("Checking certificate: {}", certSubject); // Check if this certificate matches or is signed by a dev cert for (X509Certificate devCert : devCerts) { - X500Principal devCertSubject = devCert.getSubjectX500Principal(); - X500Principal devCertIssuer = devCert.getIssuerX500Principal(); - - // Check if certificate matches dev cert (same serial/issuer or exact match) - if (cert.getSerialNumber().equals(devCert.getSerialNumber()) || - certIssuer.equals(devCertIssuer) || - cert.equals(devCert)) { - logger.debug("Trusting certificate signed by development CA: {}", certSubject); - return; // Trusted by development CA + // First check for exact match + if (cert.equals(devCert)) { + logger.debug("Trusting certificate (exact match with development cert): {}", certSubject); + return; } - // Check if this certificate's issuer matches a dev cert's subject - // (meaning the dev cert is the CA that signed this cert) - if (certIssuer.equals(devCertSubject)) { + // Then verify cryptographic signature + // Only trust certs signed by dev CAs if the dev cert is actually a CA + try { + // Check if dev cert has CA basic constraints + boolean isCA = devCert.getBasicConstraints() != -1; + if (!isCA) { + logger.trace("Development cert is not a CA, skipping signature verification: {}", devCert.getSubjectX500Principal()); + continue; + } + + // Verify that the cert was signed by the dev cert + cert.verify(devCert.getPublicKey()); logger.debug("Trusting certificate signed by development CA: {}", certSubject); return; // Trusted by development CA + } catch (Exception verifyException) { + // Signature verification failed, continue checking other dev certs + logger.trace("Signature verification failed for cert {} with dev cert {}: {}", + certSubject, devCert.getSubjectX500Principal(), verifyException.getMessage()); } } } From 58d5ae69a016a3e0aa5d775e0a0cb0a92eeeebaa Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 29 Jan 2026 17:31:10 -0600 Subject: [PATCH 04/11] Address PR review feedback - Fix logging.level namespace to match actual package after build-time rewriting (io.steeltoe.docker instead of io.steeltoe.docker.ssl) - Correct AGENTS.md patch documentation for unified diff hunk headers --- AGENTS.md | 5 +++-- config-server/patches/application.properties.patch | 2 +- eureka-server/patches/application.properties.patch | 2 +- spring-boot-admin/patches/application.properties.patch | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2034656..58ae95a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,8 +28,9 @@ Before submitting patch changes: ### Patch Format Rules 1. **Hunk headers must be accurate**: The format is `@@ -old_start,old_count +new_start,new_count @@` - - `new_count` must equal the exact number of lines with `+` prefix in the hunk - - For new file patches (`--- /dev/null`), `old_count` is 0 + - `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 2. **Trailing newlines are required**: Patch files must end with a newline character. The `patch` utility will fail with "unexpected end of file" otherwise. 3. **Preserve exact whitespace**: Context lines must match the target file exactly, including trailing spaces and tabs. 4. **New file patches**: Use `/dev/null` as the old file: diff --git a/config-server/patches/application.properties.patch b/config-server/patches/application.properties.patch index 7b35e64..0e1d748 100644 --- a/config-server/patches/application.properties.patch +++ b/config-server/patches/application.properties.patch @@ -11,4 +11,4 @@ +eureka.instance.hostname=host.docker.internal +eureka.instance.instanceId=host.docker.internal:configserver:8888 +auth.enabled=false -+logging.level.io.steeltoe.docker.ssl=INFO ++logging.level.io.steeltoe.docker=INFO diff --git a/eureka-server/patches/application.properties.patch b/eureka-server/patches/application.properties.patch index 77978de..7b42439 100644 --- a/eureka-server/patches/application.properties.patch +++ b/eureka-server/patches/application.properties.patch @@ -14,5 +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.ssl = INFO ++logging.level.io.steeltoe.docker = INFO +spring.main.lazy-initialization = true diff --git a/spring-boot-admin/patches/application.properties.patch b/spring-boot-admin/patches/application.properties.patch index 82ef4d5..6b737a6 100644 --- a/spring-boot-admin/patches/application.properties.patch +++ b/spring-boot-admin/patches/application.properties.patch @@ -3,4 +3,4 @@ @@ -0,0 +1,3 @@ +server.port=9099 +spring.thymeleaf.check-template-location=false -+logging.level.io.steeltoe.docker.ssl=INFO ++logging.level.io.steeltoe.docker=INFO From 01cfbdf5f498dd17ca18674bdfb07f6c9a07765f Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 4 Feb 2026 19:44:45 -0600 Subject: [PATCH 05/11] update default docker org --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index 5b0bbb1..c8499b0 100755 --- a/build.ps1 +++ b/build.ps1 @@ -64,7 +64,7 @@ try { $DockerOrg = $Registry } else { - $DockerOrg = "steeltoeoss" + $DockerOrg = "steeltoe.azurecr.io" } if ($Help) { From eff495c2c90d9c87e75d521f2bdb65a3ae825ad0 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 5 Feb 2026 16:32:52 -0600 Subject: [PATCH 06/11] limit ssl-related changes to SBA --- build.ps1 | 29 --- .../patches/application.properties.patch | 5 +- .../patches/application.properties.patch | 3 +- shared/ssl-config/SslTrustConfiguration.java | 193 ----------------- .../patches/enable-springbootadmin.patch | 12 +- .../spring-boot-admin-ssl-config.patch | 4 +- .../patches/ssl-trust-config.patch | 196 ++++++++++++++++++ 7 files changed, 206 insertions(+), 236 deletions(-) delete mode 100644 shared/ssl-config/SslTrustConfiguration.java create mode 100644 spring-boot-admin/patches/ssl-trust-config.patch diff --git a/build.ps1 b/build.ps1 index c8499b0..3c830c1 100755 --- a/build.ps1 +++ b/build.ps1 @@ -287,35 +287,6 @@ try { } } - # Copy shared SSL configuration files - # Get repository root (parent of workspace directory) - # We're currently in workspace/$serverName, so go up two levels to get to repo root - $currentDir = (Get-Location).Path - $workspaceDir = Split-Path -Parent $currentDir - $repoRoot = Split-Path -Parent $workspaceDir - $sharedSslDir = Join-Path $repoRoot "shared" "ssl-config" - if (Test-Path $sharedSslDir) { - # Place the shared SSL configuration into the app's base package - # so that it is discovered by Spring's component scanning. - $appJavaDir = Join-Path "src" "main" "java" "io" "steeltoe" "docker" $serverName - if (!(Test-Path $appJavaDir)) { - New-Item -ItemType Directory -Path $appJavaDir -Force | Out-Null - } - - # Copy SslTrustConfiguration.java (used by all services) - $sslTrustConfig = Join-Path $sharedSslDir "SslTrustConfiguration.java" - if (Test-Path $sslTrustConfig) { - $targetFile = Join-Path $appJavaDir "SslTrustConfiguration.java" - Write-Host "Copying shared SSL trust configuration into app package" - Copy-Item $sslTrustConfig $targetFile -Force - - # Update the package declaration so the class resides in the app's package - $packagePattern = 'package\s+io\.steeltoe\.docker\.ssl;' - $newPackageDeclaration = "package io.steeltoe.docker.$serverName;" - (Get-Content $targetFile) -replace $packagePattern, $newPackageDeclaration | Set-Content $targetFile - } - } - # Build the image $gradleArgs = @("bootBuildImage", "--imageName=$ImageNameWithTag") if ($env:GITHUB_ACTIONS -eq "true") { diff --git a/config-server/patches/application.properties.patch b/config-server/patches/application.properties.patch index 0e1d748..cda4bd4 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 2026-01-27 00:00:00.000000000 -0500 -@@ -0,0 +1,11 @@ ++++ configserver/src/main/resources/application.properties 2025-08-15 13:15:18.461432100 -0500 +@@ -0,0 +1,10 @@ +server.port=8888 +spring.cloud.config.server.git.uri=https://github.com/spring-cloud-samples/config-repo +eureka.client.enabled=false @@ -11,4 +11,3 @@ +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/eureka-server/patches/application.properties.patch b/eureka-server/patches/application.properties.patch index 7b42439..ec109cb 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,15 @@ +@@ -0,0 +1,14 @@ +server.port = 8761 +eureka.client.fetch-registry = false +eureka.client.register-with-eureka = false @@ -14,5 +14,4 @@ +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/shared/ssl-config/SslTrustConfiguration.java b/shared/ssl-config/SslTrustConfiguration.java deleted file mode 100644 index 71c842f..0000000 --- a/shared/ssl-config/SslTrustConfiguration.java +++ /dev/null @@ -1,193 +0,0 @@ -package io.steeltoe.docker.ssl; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import javax.security.auth.x500.X500Principal; - -/** - * SSL Trust Configuration - * - * This configuration class provides SSL certificate trust support for development environments. - * It automatically loads certificates from environment variables or standard locations and - * creates a custom TrustManager that trusts both standard CA certificates and development - * certificates (e.g., Aspire development certificates). - * - *

Certificate locations checked (in order): - *

    - *
  • {@code SSL_CERT_DIR} environment variable (directory containing certificates)
  • - *
  • {@code /usr/lib/ssl/aspire} (default development certificate directory, e.g., for Aspire)
  • - *
  • {@code SSL_CERT_FILE} environment variable (single certificate file)
  • - *
- * - *

Supported certificate formats: .pem, .crt, .cer - * - *

Example usage with Aspire: - *

- * // Aspire automatically sets SSL_CERT_DIR or mounts certificates at /usr/lib/ssl/aspire
- * // This configuration will automatically detect and trust those certificates
- * 
- */ -@Configuration -public class SslTrustConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(SslTrustConfiguration.class); - - @Bean - public X509TrustManager sslTrustManager() { - try { - TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - defaultTmf.init((KeyStore) null); - javax.net.ssl.TrustManager[] trustManagers = defaultTmf.getTrustManagers(); - if (trustManagers == null || trustManagers.length == 0 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("No X509TrustManager available from default TrustManagerFactory"); - } - X509TrustManager defaultTrustManager = (X509TrustManager) trustManagers[0]; - - List devCerts = loadDevelopmentCertificates(); - if (devCerts.isEmpty()) { - logger.info("SSL trust: Using default trust manager (no development certificates found)"); - return defaultTrustManager; - } - - logger.info("SSL trust: Loaded {} development certificate(s)", devCerts.size()); - return new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - defaultTrustManager.checkClientTrusted(chain, authType); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - try { - defaultTrustManager.checkServerTrusted(chain, authType); - } catch (CertificateException e) { - // If default validation fails, check if any certificate in the chain - // is signed by or matches a development certificate - logger.debug("Default trust validation failed, checking development certificates..."); - for (X509Certificate cert : chain) { - X500Principal certSubject = cert.getSubjectX500Principal(); - logger.trace("Checking certificate: {}", certSubject); - - // Check if this certificate matches or is signed by a dev cert - for (X509Certificate devCert : devCerts) { - // First check for exact match - if (cert.equals(devCert)) { - logger.debug("Trusting certificate (exact match with development cert): {}", certSubject); - return; - } - - // Then verify cryptographic signature - // Only trust certs signed by dev CAs if the dev cert is actually a CA - try { - // Check if dev cert has CA basic constraints - boolean isCA = devCert.getBasicConstraints() != -1; - if (!isCA) { - logger.trace("Development cert is not a CA, skipping signature verification: {}", devCert.getSubjectX500Principal()); - continue; - } - - // Verify that the cert was signed by the dev cert - cert.verify(devCert.getPublicKey()); - logger.debug("Trusting certificate signed by development CA: {}", certSubject); - return; // Trusted by development CA - } catch (Exception verifyException) { - // Signature verification failed, continue checking other dev certs - logger.trace("Signature verification failed for cert {} with dev cert {}: {}", - certSubject, devCert.getSubjectX500Principal(), verifyException.getMessage()); - } - } - } - // If we get here, the certificate chain doesn't include any development certificates - logger.warn("Certificate validation failed and no development certificate found in chain"); - throw e; - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - X509Certificate[] defaultCerts = defaultTrustManager.getAcceptedIssuers(); - X509Certificate[] allCerts = new X509Certificate[defaultCerts.length + devCerts.size()]; - System.arraycopy(defaultCerts, 0, allCerts, 0, defaultCerts.length); - System.arraycopy(devCerts.toArray(new X509Certificate[0]), 0, allCerts, defaultCerts.length, devCerts.size()); - return allCerts; - } - }; - } catch (Exception e) { - logger.error("Failed to create SSL trust manager, using default", e); - try { - TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - defaultTmf.init((KeyStore) null); - javax.net.ssl.TrustManager[] trustManagers = defaultTmf.getTrustManagers(); - if (trustManagers == null || trustManagers.length == 0 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("No X509TrustManager available from default TrustManagerFactory"); - } - return (X509TrustManager) trustManagers[0]; - } catch (Exception ex) { - logger.error("Failed to create default trust manager", ex); - throw new RuntimeException("Failed to create trust manager", ex); - } - } - } - - private List loadDevelopmentCertificates() { - List certificates = new ArrayList<>(); - logger.debug("Loading development certificates..."); - try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - String[] certPaths = { - System.getenv("SSL_CERT_DIR"), - "/usr/lib/ssl/aspire", - System.getenv("SSL_CERT_FILE") - }; - - logger.trace("Checking certificate paths: SSL_CERT_DIR={}, /usr/lib/ssl/aspire, SSL_CERT_FILE={}", - System.getenv("SSL_CERT_DIR"), System.getenv("SSL_CERT_FILE")); - - for (String certPath : certPaths) { - if (certPath == null) continue; - Path path = Paths.get(certPath); - if (Files.isDirectory(path)) { - logger.debug("Scanning directory for certificates: {}", certPath); - try (var stream = Files.walk(path)) { - stream.filter(Files::isRegularFile) - .filter(p -> p.toString().matches(".*\\.(pem|crt|cer)$")) - .forEach(p -> { - try { - try (var inputStream = Files.newInputStream(p)) { - certificates.add((X509Certificate) certFactory.generateCertificate(inputStream)); - logger.debug("Loaded certificate: {}", p); - } - } catch (Exception e) { - logger.warn("Failed to load certificate {}: {}", p, e.getMessage()); - } - }); - } - } else if (Files.isRegularFile(path)) { - try (var inputStream = Files.newInputStream(path)) { - certificates.add((X509Certificate) certFactory.generateCertificate(inputStream)); - logger.debug("Loaded certificate: {}", path); - } catch (Exception e) { - logger.warn("Failed to load certificate {}: {}", path, e.getMessage()); - } - } - } - } catch (Exception e) { - logger.error("Error loading development certificates", e); - } - return certificates; - } -} diff --git a/spring-boot-admin/patches/enable-springbootadmin.patch b/spring-boot-admin/patches/enable-springbootadmin.patch index 877a062..f5010d3 100644 --- a/spring-boot-admin/patches/enable-springbootadmin.patch +++ b/spring-boot-admin/patches/enable-springbootadmin.patch @@ -1,22 +1,20 @@ --- ./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 -@@ -1,13 +1,18 @@ +@@ -1,13 +1,17 @@ package io.steeltoe.docker.springbootadmin; - + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import de.codecentric.boot.admin.server.config.EnableAdminServer; -+import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; - + +@EnableAdminServer -+@ComponentScan(basePackages = { "io.steeltoe.docker.springbootadmin", "io.steeltoe.docker.ssl" }) +@Import(SteeltoeAdminConfiguration.class) @SpringBootApplication public class SpringBootAdmin { - + public static void main(String[] args) { SpringApplication.run(SpringBootAdmin.class, args); } -- + } diff --git a/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch b/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch index 2aec452..966fe41 100644 --- a/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch +++ b/spring-boot-admin/patches/spring-boot-admin-ssl-config.patch @@ -1,7 +1,7 @@ --- /dev/null -+++ ./src/main/java/io/steeltoe/docker/ssl/SpringBootAdminSslConfiguration.java 2026-01-27 00:00:00.000000000 +0000 ++++ ./src/main/java/io/steeltoe/docker/springbootadmin/SpringBootAdminSslConfiguration.java 2026-01-27 00:00:00.000000000 +0000 @@ -0,0 +1,79 @@ -+package io.steeltoe.docker.ssl; ++package io.steeltoe.docker.springbootadmin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; diff --git a/spring-boot-admin/patches/ssl-trust-config.patch b/spring-boot-admin/patches/ssl-trust-config.patch new file mode 100644 index 0000000..a434e81 --- /dev/null +++ b/spring-boot-admin/patches/ssl-trust-config.patch @@ -0,0 +1,196 @@ +--- /dev/null ++++ ./src/main/java/io/steeltoe/docker/springbootadmin/SslTrustConfiguration.java 2026-01-27 00:00:00.000000000 +0000 +@@ -0,0 +1,193 @@ ++package io.steeltoe.docker.springbootadmin; ++ ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.springframework.context.annotation.Bean; ++import org.springframework.context.annotation.Configuration; ++ ++import javax.net.ssl.TrustManagerFactory; ++import javax.net.ssl.X509TrustManager; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.Paths; ++import java.security.KeyStore; ++import java.security.cert.CertificateException; ++import java.security.cert.CertificateFactory; ++import java.security.cert.X509Certificate; ++import java.util.ArrayList; ++import java.util.List; ++import javax.security.auth.x500.X500Principal; ++ ++/** ++ * SSL Trust Configuration ++ * ++ * This configuration class provides SSL certificate trust support for development environments. ++ * It automatically loads certificates from environment variables or standard locations and ++ * creates a custom TrustManager that trusts both standard CA certificates and development ++ * certificates (e.g., Aspire development certificates). ++ * ++ *

Certificate locations checked (in order): ++ *

    ++ *
  • {@code SSL_CERT_DIR} environment variable (directory containing certificates)
  • ++ *
  • {@code /usr/lib/ssl/aspire} (default development certificate directory, e.g., for Aspire)
  • ++ *
  • {@code SSL_CERT_FILE} environment variable (single certificate file)
  • ++ *
++ * ++ *

Supported certificate formats: .pem, .crt, .cer ++ * ++ *

Example usage with Aspire: ++ *

++ * // Aspire automatically sets SSL_CERT_DIR or mounts certificates at /usr/lib/ssl/aspire
++ * // This configuration will automatically detect and trust those certificates
++ * 
++ */ ++@Configuration ++public class SslTrustConfiguration { ++ ++ private static final Logger logger = LoggerFactory.getLogger(SslTrustConfiguration.class); ++ ++ @Bean ++ public X509TrustManager sslTrustManager() { ++ try { ++ TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); ++ defaultTmf.init((KeyStore) null); ++ javax.net.ssl.TrustManager[] trustManagers = defaultTmf.getTrustManagers(); ++ if (trustManagers == null || trustManagers.length == 0 || !(trustManagers[0] instanceof X509TrustManager)) { ++ throw new IllegalStateException("No X509TrustManager available from default TrustManagerFactory"); ++ } ++ X509TrustManager defaultTrustManager = (X509TrustManager) trustManagers[0]; ++ ++ List devCerts = loadDevelopmentCertificates(); ++ if (devCerts.isEmpty()) { ++ logger.info("SSL trust: Using default trust manager (no development certificates found)"); ++ return defaultTrustManager; ++ } ++ ++ logger.info("SSL trust: Loaded {} development certificate(s)", devCerts.size()); ++ return new X509TrustManager() { ++ @Override ++ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { ++ defaultTrustManager.checkClientTrusted(chain, authType); ++ } ++ ++ @Override ++ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { ++ try { ++ defaultTrustManager.checkServerTrusted(chain, authType); ++ } catch (CertificateException e) { ++ // If default validation fails, check if any certificate in the chain ++ // is signed by or matches a development certificate ++ logger.debug("Default trust validation failed, checking development certificates..."); ++ for (X509Certificate cert : chain) { ++ X500Principal certSubject = cert.getSubjectX500Principal(); ++ logger.trace("Checking certificate: {}", certSubject); ++ ++ // Check if this certificate matches or is signed by a dev cert ++ for (X509Certificate devCert : devCerts) { ++ // First check for exact match ++ if (cert.equals(devCert)) { ++ logger.debug("Trusting certificate (exact match with development cert): {}", certSubject); ++ return; ++ } ++ ++ // Then verify cryptographic signature ++ // Only trust certs signed by dev CAs if the dev cert is actually a CA ++ try { ++ // Check if dev cert has CA basic constraints ++ boolean isCA = devCert.getBasicConstraints() != -1; ++ if (!isCA) { ++ logger.trace("Development cert is not a CA, skipping signature verification: {}", devCert.getSubjectX500Principal()); ++ continue; ++ } ++ ++ // Verify that the cert was signed by the dev cert ++ cert.verify(devCert.getPublicKey()); ++ logger.debug("Trusting certificate signed by development CA: {}", certSubject); ++ return; // Trusted by development CA ++ } catch (Exception verifyException) { ++ // Signature verification failed, continue checking other dev certs ++ logger.trace("Signature verification failed for cert {} with dev cert {}: {}", ++ certSubject, devCert.getSubjectX500Principal(), verifyException.getMessage()); ++ } ++ } ++ } ++ // If we get here, the certificate chain doesn't include any development certificates ++ logger.warn("Certificate validation failed and no development certificate found in chain"); ++ throw e; ++ } ++ } ++ ++ @Override ++ public X509Certificate[] getAcceptedIssuers() { ++ X509Certificate[] defaultCerts = defaultTrustManager.getAcceptedIssuers(); ++ X509Certificate[] allCerts = new X509Certificate[defaultCerts.length + devCerts.size()]; ++ System.arraycopy(defaultCerts, 0, allCerts, 0, defaultCerts.length); ++ System.arraycopy(devCerts.toArray(new X509Certificate[0]), 0, allCerts, defaultCerts.length, devCerts.size()); ++ return allCerts; ++ } ++ }; ++ } catch (Exception e) { ++ logger.error("Failed to create SSL trust manager, using default", e); ++ try { ++ TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); ++ defaultTmf.init((KeyStore) null); ++ javax.net.ssl.TrustManager[] trustManagers = defaultTmf.getTrustManagers(); ++ if (trustManagers == null || trustManagers.length == 0 || !(trustManagers[0] instanceof X509TrustManager)) { ++ throw new IllegalStateException("No X509TrustManager available from default TrustManagerFactory"); ++ } ++ return (X509TrustManager) trustManagers[0]; ++ } catch (Exception ex) { ++ logger.error("Failed to create default trust manager", ex); ++ throw new RuntimeException("Failed to create trust manager", ex); ++ } ++ } ++ } ++ ++ private List loadDevelopmentCertificates() { ++ List certificates = new ArrayList<>(); ++ logger.debug("Loading development certificates..."); ++ try { ++ CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); ++ String[] certPaths = { ++ System.getenv("SSL_CERT_DIR"), ++ "/usr/lib/ssl/aspire", ++ System.getenv("SSL_CERT_FILE") ++ }; ++ ++ logger.trace("Checking certificate paths: SSL_CERT_DIR={}, /usr/lib/ssl/aspire, SSL_CERT_FILE={}", ++ System.getenv("SSL_CERT_DIR"), System.getenv("SSL_CERT_FILE")); ++ ++ for (String certPath : certPaths) { ++ if (certPath == null) continue; ++ Path path = Paths.get(certPath); ++ if (Files.isDirectory(path)) { ++ logger.debug("Scanning directory for certificates: {}", certPath); ++ try (var stream = Files.walk(path)) { ++ stream.filter(Files::isRegularFile) ++ .filter(p -> p.toString().matches(".*\\.(pem|crt|cer)$")) ++ .forEach(p -> { ++ try { ++ try (var inputStream = Files.newInputStream(p)) { ++ certificates.add((X509Certificate) certFactory.generateCertificate(inputStream)); ++ logger.debug("Loaded certificate: {}", p); ++ } ++ } catch (Exception e) { ++ logger.warn("Failed to load certificate {}: {}", p, e.getMessage()); ++ } ++ }); ++ } ++ } else if (Files.isRegularFile(path)) { ++ try (var inputStream = Files.newInputStream(path)) { ++ certificates.add((X509Certificate) certFactory.generateCertificate(inputStream)); ++ logger.debug("Loaded certificate: {}", path); ++ } catch (Exception e) { ++ logger.warn("Failed to load certificate {}: {}", path, e.getMessage()); ++ } ++ } ++ } ++ } catch (Exception e) { ++ logger.error("Error loading development certificates", e); ++ } ++ return certificates; ++ } ++} From 8c58e1c505ecfb7baeba7b28308c34236b816ec1 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 5 Feb 2026 17:28:32 -0600 Subject: [PATCH 07/11] apply patches with "git apply" instead of "patch" --- build.ps1 | 60 ++----------------- .../patches/enableconfigserver.patch | 5 +- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/build.ps1 b/build.ps1 index 3c830c1..a2eb292 100755 --- a/build.ps1 +++ b/build.ps1 @@ -149,14 +149,8 @@ try { Invoke-Expression $docker_command } else { - if (!(Get-Command "patch" -ErrorAction SilentlyContinue)) { - if (Test-Path "$Env:ProgramFiles\Git\usr\bin\patch.exe") { - Write-Host "'patch' command not found, but Git is installed; adding Git usr\bin to PATH" - $env:Path += ";$Env:ProgramFiles\Git\usr\bin" - } - else { - throw "'patch' command not found" - } + if (!(Get-Command "git" -ErrorAction SilentlyContinue)) { + throw "'git' command not found" } switch ($Name) { @@ -238,53 +232,11 @@ try { # Apply patches foreach ($patch in Get-ChildItem -Path (Join-Path $ImageDirectory patches) -Filter "*.patch") { Write-Host "Applying patch $($patch.Name)" - $patchContent = Get-Content $patch -Raw - $patchContent | & patch -p1 - $exitCode = $LASTEXITCODE - if ($exitCode -ne 0) { - # Check if this is a "new file" patch (old file is /dev/null) - # New file patches may return non-zero exit codes but still succeed - $isNewFilePatch = $patchContent -match '(?m)^--- /dev/null' - if ($isNewFilePatch) { - # Verify the file was actually created by checking if target exists - # Extract the target file path from the +++ line (first one after --- /dev/null) - $targetFile = $null - $lines = $patchContent -split "`n" - $foundDevNull = $false - foreach ($line in $lines) { - if (-not $foundDevNull) { - if ($line -match '^--- /dev/null') { - $foundDevNull = $true - } - continue - } - if ($line -match '^\+\+\+\s+([^\t\r\n]+)') { - $targetFile = $matches[1] -replace '^\./', '' - break - } - } - if ($targetFile) { - # Remove any trailing whitespace or timestamp - $targetFile = $targetFile.Trim() - if ($targetFile -and (Test-Path $targetFile)) { - Write-Host "Patch $($patch.Name) created new file successfully: $targetFile" - } else { - Write-Host "Warning: Patch $($patch.Name) appears to be a new file patch but target not found: $targetFile" - throw "Patch $($patch.Name) failed with exit code $exitCode" - } - } else { - Write-Host "Warning: Patch $($patch.Name) appears to be a new file patch but could not determine target file path" - throw "Patch $($patch.Name) failed with exit code $exitCode" - } - } else { - Write-Host "Error: Patch $($patch.Name) failed with exit code $exitCode" - Write-Host "Patch content preview:" - Get-Content $patch | Select-Object -First 10 | ForEach-Object { Write-Host " $_" } - throw "Patch $($patch.Name) failed with exit code $exitCode" - } - } else { - Write-Host "Patch $($patch.Name) applied successfully" + 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" } # Build the image diff --git a/config-server/patches/enableconfigserver.patch b/config-server/patches/enableconfigserver.patch index 7ffc6be..c971079 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 2026-01-27 00:00:00.000000000 -0600 -@@ -1,10 +1,20 @@ ++++ configserver/src/main/java/io/steeltoe/docker/configserver/ConfigServer.java 2024-04-02 13:40:40.622446300 -0600 +@@ -1,12 +1,22 @@ package io.steeltoe.docker.configserver; +import org.slf4j.Logger; @@ -21,3 +21,4 @@ + Package pkg = EnableConfigServer.class.getPackage(); + logger.info("{} {} by {}", pkg.getImplementationTitle(), pkg.getImplementationVersion(), pkg.getImplementationVendor()); SpringApplication.run(ConfigServer.class, args); + } From 88b49e7349da081caf90532de536712fef37f9ec Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 6 Feb 2026 10:36:52 -0600 Subject: [PATCH 08/11] pr feedback - drop tags from readmes - remove hardcoded aspire cert paths, fix env dir list parsing - fix broken-again link - sync agents.md --- AGENTS.md | 15 ++--- config-server/README.md | 6 +- eureka-server/README.md | 2 +- spring-boot-admin/README.md | 2 +- .../patches/enable-springbootadmin.patch | 15 ++--- .../spring-boot-admin-ssl-config.patch | 2 +- .../patches/ssl-trust-config.patch | 57 ++++++++++--------- .../patches/steeltoe-admin-config.patch | 2 +- uaa-server/README.md | 8 +-- 9 files changed, 54 insertions(+), 55 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 58ae95a..f5e7a80 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,22 +17,23 @@ Instead, to get a fresh build: Before submitting patch changes: -1. Run a dry-run of each patch: `Get-Content patch.patch | & patch --dry-run -p1` +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 ## Patch Files -**CRITICAL**: When modifying patch files (`.patch` files in `patches/` directories), you MUST update the line numbers in the hunk headers when adding or removing lines. +The build script uses `git apply --unidiff-zero --recount --ignore-whitespace` to apply patches, which is more forgiving than the traditional `patch` command. ### Patch Format Rules -1. **Hunk headers must be accurate**: The format is `@@ -old_start,old_count +new_start,new_count @@` +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 -2. **Trailing newlines are required**: Patch files must end with a newline character. The `patch` utility will fail with "unexpected end of file" otherwise. -3. **Preserve exact whitespace**: Context lines must match the target file exactly, including trailing spaces and tabs. + - 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: ```diff @@ -46,7 +47,7 @@ Before submitting patch changes: ### Example -If a patch adds 1 line, the hunk header must reflect this: +If a patch adds 1 line, the hunk header should reflect this: ```diff -@@ -37,3 +37,10 @@ @@ -55,4 +56,4 @@ If a patch adds 1 line, the hunk header must reflect this: ### Why This Matters -Incorrect line numbers cause patch application to fail, breaking the build process. +While `git apply --recount` can fix minor line count issues, keeping patches accurate ensures reliable application and easier debugging. diff --git a/config-server/README.md b/config-server/README.md index ffbb8b7..91ac6f0 100644 --- a/config-server/README.md +++ b/config-server/README.md @@ -7,13 +7,13 @@ Image for SteeltoeOSS local development with [Spring Cloud Config Server](https: Default configuration: ```shell -docker run --publish 8888:8888 steeltoe.azurecr.io/config-server:4 +docker run --publish 8888:8888 steeltoe.azurecr.io/config-server ``` Custom git repo configuration: ```shell -docker run --publish 8888:8888 steeltoe.azurecr.io/config-server:4 \ +docker run --publish 8888:8888 steeltoe.azurecr.io/config-server \ --spring.cloud.config.server.git.uri=https://github.com/myorg/myrepo.git ``` @@ -28,7 +28,7 @@ docker run --publish 8888:8888 --volume /path/to/my/config:/config steeltoe.azur With basic auth: ```shell -docker run --publish 8888:8888 steeltoe.azurecr.io/config-server:4 \ +docker run --publish 8888:8888 steeltoe.azurecr.io/config-server \ --auth.enabled=true \ --auth.username=myCustomUser \ --auth.password=myCustomPassword diff --git a/eureka-server/README.md b/eureka-server/README.md index 26772fb..c81667c 100644 --- a/eureka-server/README.md +++ b/eureka-server/README.md @@ -5,7 +5,7 @@ Image for SteeltoeOSS local development with [Spring Cloud Eureka Server](https: ## Running ```shell -docker run --rm -it --pull=always -p 8761:8761 --name steeltoe-eureka steeltoe.azurecr.io/eureka-server:4 +docker run --rm -it --pull=always -p 8761:8761 --name steeltoe-eureka steeltoe.azurecr.io/eureka-server ``` ## Resources diff --git a/spring-boot-admin/README.md b/spring-boot-admin/README.md index 971e83d..4bebf29 100644 --- a/spring-boot-admin/README.md +++ b/spring-boot-admin/README.md @@ -5,5 +5,5 @@ Image for SteeltoeOSS local development with This configuration class provides SSL certificate trust support for development environments. + * It automatically loads certificates from environment variables or standard locations and + * creates a custom TrustManager that trusts both standard CA certificates and development -+ * certificates (e.g., Aspire development certificates). ++ * certificates (e.g., ASP.NET Core development certificates).

+ * -+ *

Certificate locations checked (in order): ++ *

Certificate locations checked:

+ *
    -+ *
  • {@code SSL_CERT_DIR} environment variable (directory containing certificates)
  • -+ *
  • {@code /usr/lib/ssl/aspire} (default development certificate directory, e.g., for Aspire)
  • -+ *
  • {@code SSL_CERT_FILE} environment variable (single certificate file)
  • ++ *
  • {@code SSL_CERT_DIR} environment variable - colon-separated list of directories containing certificates
  • ++ *
  • {@code SSL_CERT_FILE} environment variable - path to a single certificate file
  • + *
+ * -+ *

Supported certificate formats: .pem, .crt, .cer -+ * -+ *

Example usage with Aspire: -+ *

-+ * // Aspire automatically sets SSL_CERT_DIR or mounts certificates at /usr/lib/ssl/aspire
-+ * // This configuration will automatically detect and trust those certificates
-+ * 
++ *

Supported certificate formats: .pem, .crt, .cer

+ */ +@Configuration +public class SslTrustConfiguration { @@ -84,7 +77,7 @@ + for (X509Certificate cert : chain) { + X500Principal certSubject = cert.getSubjectX500Principal(); + logger.trace("Checking certificate: {}", certSubject); -+ ++ + // Check if this certificate matches or is signed by a dev cert + for (X509Certificate devCert : devCerts) { + // First check for exact match @@ -92,7 +85,7 @@ + logger.debug("Trusting certificate (exact match with development cert): {}", certSubject); + return; + } -+ ++ + // Then verify cryptographic signature + // Only trust certs signed by dev CAs if the dev cert is actually a CA + try { @@ -102,14 +95,14 @@ + logger.trace("Development cert is not a CA, skipping signature verification: {}", devCert.getSubjectX500Principal()); + continue; + } -+ ++ + // Verify that the cert was signed by the dev cert + cert.verify(devCert.getPublicKey()); + logger.debug("Trusting certificate signed by development CA: {}", certSubject); + return; // Trusted by development CA + } catch (Exception verifyException) { + // Signature verification failed, continue checking other dev certs -+ logger.trace("Signature verification failed for cert {} with dev cert {}: {}", ++ logger.trace("Signature verification failed for cert {} with dev cert {}: {}", + certSubject, devCert.getSubjectX500Principal(), verifyException.getMessage()); + } + } @@ -151,17 +144,27 @@ + logger.debug("Loading development certificates..."); + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); -+ String[] certPaths = { -+ System.getenv("SSL_CERT_DIR"), -+ "/usr/lib/ssl/aspire", -+ System.getenv("SSL_CERT_FILE") -+ }; ++ List certPaths = new ArrayList<>(); ++ ++ // SSL_CERT_DIR can be colon-separated list of directories (OpenSSL standard) ++ String sslCertDir = System.getenv("SSL_CERT_DIR"); ++ if (sslCertDir != null && !sslCertDir.isEmpty()) { ++ for (String dir : sslCertDir.split(":")) { ++ if (!dir.isEmpty()) { ++ certPaths.add(dir); ++ } ++ } ++ } ++ ++ // SSL_CERT_FILE for single certificate file ++ String sslCertFile = System.getenv("SSL_CERT_FILE"); ++ if (sslCertFile != null && !sslCertFile.isEmpty()) { ++ certPaths.add(sslCertFile); ++ } + -+ logger.trace("Checking certificate paths: SSL_CERT_DIR={}, /usr/lib/ssl/aspire, SSL_CERT_FILE={}", -+ System.getenv("SSL_CERT_DIR"), System.getenv("SSL_CERT_FILE")); ++ logger.debug("Checking certificate paths: {}", certPaths); + + for (String certPath : certPaths) { -+ if (certPath == null) continue; + Path path = Paths.get(certPath); + if (Files.isDirectory(path)) { + logger.debug("Scanning directory for certificates: {}", certPath); @@ -186,6 +189,8 @@ + } catch (Exception e) { + logger.warn("Failed to load certificate {}: {}", path, e.getMessage()); + } ++ } else { ++ logger.trace("Path does not exist or is not accessible: {}", certPath); + } + } + } catch (Exception e) { diff --git a/spring-boot-admin/patches/steeltoe-admin-config.patch b/spring-boot-admin/patches/steeltoe-admin-config.patch index 783b16c..b4f74cd 100644 --- a/spring-boot-admin/patches/steeltoe-admin-config.patch +++ b/spring-boot-admin/patches/steeltoe-admin-config.patch @@ -81,7 +81,7 @@ + log.info("Steeltoe-compatible WebClient configuration complete"); + + } catch (ClassNotFoundException e) { -+ log.error("Failed to load SBA Response classes: {}", e.getMessage()); ++ log.error("Failed to load SBA Response class: {}", e.getMessage()); + } + }; + } diff --git a/uaa-server/README.md b/uaa-server/README.md index 5cd53ce..3a12b70 100644 --- a/uaa-server/README.md +++ b/uaa-server/README.md @@ -7,13 +7,13 @@ This directory contains resources for building a [CloudFoundry User Account and To run this image locally: ```shell -docker run -it -p 8080:8080 --name steeltoe-uaa steeltoe.azurecr.io/uaa-server:78 +docker run -it -p 8080:8080 --name steeltoe-uaa steeltoe.azurecr.io/uaa-server ``` To run this image locally, overwriting the included `uaa.yml` file: ```shell -docker run -it -p 8080:8080 --name steeltoe-uaa -v $pwd/uaa.yml:/uaa/uaa.yml steeltoe.azurecr.io/uaa-server:78 +docker run -it -p 8080:8080 --name steeltoe-uaa -v $pwd/uaa.yml:/uaa/uaa.yml steeltoe.azurecr.io/uaa-server ``` ## Customizing for your Cloud Foundry environment @@ -27,6 +27,6 @@ These instructions will help you build and deploy a custom image to use as an id 1. `.\build.ps1 uaa-server`. 1. Push the image to an image repository accessible from your Cloud Foundry environment. 1. Deploy the image with a command similar to this: - * `cf push steeltoe-uaa --docker-image steeltoe.azurecr.io/uaa-server:78` -1. (Operator task) [Add the new identity provider with OpenID Connect](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/single-sign-on-for-tanzu/1-16/sso-tanzu/operator-guide.html#config-ext-id) + * `cf push steeltoe-uaa --docker-image steeltoe.azurecr.io/uaa-server` +1. (Operator task) [Add the new identity provider with OpenID Connect](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform/single-sign-on/1-16/sso/configure-external-id.html#config-ext-prov) * Use the `ssotile` credentials from uaa.yml From 2956bc1ebac4f2adae6aa745020c93685b127a9f Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 6 Feb 2026 11:49:34 -0600 Subject: [PATCH 09/11] enable class data sharing for non-native images (.5 second faster start times) --- config-server/patches/build.gradle.patch | 5 +++-- eureka-server/patches/build.gradle.patch | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config-server/patches/build.gradle.patch b/config-server/patches/build.gradle.patch index b6d01c6..fa07a86 100644 --- a/config-server/patches/build.gradle.patch +++ b/config-server/patches/build.gradle.patch @@ -1,6 +1,6 @@ --- ./build.gradle 2025-09-30 14:48:20.000000000 -0500 +++ ./build.gradle 2025-09-30 14:49:16.584226000 -0500 -@@ -41,3 +41,10 @@ +@@ -41,3 +41,11 @@ tasks.named('test') { useJUnitPlatform() } @@ -8,6 +8,7 @@ +bootBuildImage { + createdDate = "now" + environment = [ -+ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true" ++ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", ++ "BP_JVM_CDS_ENABLED": "true" + ] +} diff --git a/eureka-server/patches/build.gradle.patch b/eureka-server/patches/build.gradle.patch index b6d01c6..fa07a86 100644 --- a/eureka-server/patches/build.gradle.patch +++ b/eureka-server/patches/build.gradle.patch @@ -1,6 +1,6 @@ --- ./build.gradle 2025-09-30 14:48:20.000000000 -0500 +++ ./build.gradle 2025-09-30 14:49:16.584226000 -0500 -@@ -41,3 +41,10 @@ +@@ -41,3 +41,11 @@ tasks.named('test') { useJUnitPlatform() } @@ -8,6 +8,7 @@ +bootBuildImage { + createdDate = "now" + environment = [ -+ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true" ++ "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", ++ "BP_JVM_CDS_ENABLED": "true" + ] +} From 8c9534c224079dc16c3f23b945eea8a07f5ff700 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 6 Feb 2026 19:43:29 -0600 Subject: [PATCH 10/11] BP_JVM_CDS_ENABLED => BP_JVM_AOTCACHE_ENABLED - remove last tag from readme - un-reorder lines for sba --- config-server/README.md | 4 ++-- config-server/patches/build.gradle.patch | 2 +- eureka-server/patches/build.gradle.patch | 2 +- spring-boot-admin/patches/enable-springbootadmin.patch | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config-server/README.md b/config-server/README.md index 91ac6f0..269e66d 100644 --- a/config-server/README.md +++ b/config-server/README.md @@ -20,7 +20,7 @@ docker run --publish 8888:8888 steeltoe.azurecr.io/config-server \ Local file system configuration: ```shell -docker run --publish 8888:8888 --volume /path/to/my/config:/config steeltoe.azurecr.io/config-server:4 \ +docker run --publish 8888:8888 --volume /path/to/my/config:/config steeltoe.azurecr.io/config-server \ --spring.profiles.active=native \ --spring.cloud.config.server.native.searchLocations=file:///config ``` @@ -40,6 +40,6 @@ docker run --publish 8888:8888 steeltoe.azurecr.io/config-server \ | ---- | ----------- | | /_{app}_/_{profile}_ | Configuration data for app in Spring profile | | /_{app}_/_{profile}_/_{label}_ | Add a git label | -| /_{app}_/_{profiles}/{label}_/_{path}_ | Environment-specific plain text config file at _{path}_| +| /_{app}_/_{profiles}/{label}_/_{path}_ | Environment-specific plain text config file at _{path}_ | _Example:_ diff --git a/config-server/patches/build.gradle.patch b/config-server/patches/build.gradle.patch index fa07a86..e72ffbe 100644 --- a/config-server/patches/build.gradle.patch +++ b/config-server/patches/build.gradle.patch @@ -9,6 +9,6 @@ + createdDate = "now" + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", -+ "BP_JVM_CDS_ENABLED": "true" ++ "BP_JVM_AOTCACHE_ENABLED": "true" + ] +} diff --git a/eureka-server/patches/build.gradle.patch b/eureka-server/patches/build.gradle.patch index fa07a86..e72ffbe 100644 --- a/eureka-server/patches/build.gradle.patch +++ b/eureka-server/patches/build.gradle.patch @@ -9,6 +9,6 @@ + createdDate = "now" + environment = [ + "BP_SPRING_CLOUD_BINDINGS_DISABLED": "true", -+ "BP_JVM_CDS_ENABLED": "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 c561e71..38e5c2a 100644 --- a/spring-boot-admin/patches/enable-springbootadmin.patch +++ b/spring-boot-admin/patches/enable-springbootadmin.patch @@ -7,7 +7,7 @@ +import org.springframework.context.annotation.Import; +import de.codecentric.boot.admin.server.config.EnableAdminServer; + @SpringBootApplication +@EnableAdminServer +@Import(SteeltoeAdminConfiguration.class) - @SpringBootApplication public class SpringBootAdmin { From 9821cd259da7d03628272b507644cddd361f5fd2 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 9 Feb 2026 11:51:55 -0600 Subject: [PATCH 11/11] Build on PR to any branch, but with more restictive path filter --- .github/workflows/build_config_server.yaml | 8 ++++---- .github/workflows/build_eureka_server.yaml | 8 ++++---- .github/workflows/build_springboot_admin_server.yaml | 8 ++++---- .github/workflows/build_uaa_server.yaml | 12 ++++++++---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build_config_server.yaml b/.github/workflows/build_config_server.yaml index 995323b..998dc2f 100644 --- a/.github/workflows/build_config_server.yaml +++ b/.github/workflows/build_config_server.yaml @@ -2,18 +2,18 @@ name: Build Config Server on: pull_request: - branches: - - main paths: - '.github/workflows/build_config_server.yaml' - - 'config-server/**' + - 'config-server/metadata/*' + - 'config-server/patches/*' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_config_server.yaml' - - 'config-server/**' + - 'config-server/metadata/*' + - 'config-server/patches/*' - 'build.ps1' concurrency: diff --git a/.github/workflows/build_eureka_server.yaml b/.github/workflows/build_eureka_server.yaml index 5777ecc..6ac942a 100644 --- a/.github/workflows/build_eureka_server.yaml +++ b/.github/workflows/build_eureka_server.yaml @@ -2,18 +2,18 @@ name: Build Eureka Server on: pull_request: - branches: - - main paths: - '.github/workflows/build_eureka_server.yaml' - - 'eureka-server/**' + - 'eureka-server/metadata/*' + - 'eureka-server/patches/*' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_eureka_server.yaml' - - 'eureka-server/**' + - 'eureka-server/metadata/*' + - 'eureka-server/patches/*' - 'build.ps1' concurrency: diff --git a/.github/workflows/build_springboot_admin_server.yaml b/.github/workflows/build_springboot_admin_server.yaml index e47e85b..d2ba5cf 100644 --- a/.github/workflows/build_springboot_admin_server.yaml +++ b/.github/workflows/build_springboot_admin_server.yaml @@ -2,18 +2,18 @@ name: Build Spring Boot Admin Server on: pull_request: - branches: - - main paths: - '.github/workflows/build_springboot_admin_server.yaml' - - 'spring-boot-admin/**' + - 'spring-boot-admin/metadata/*' + - 'spring-boot-admin/patches/*' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_springboot_admin_server.yaml' - - 'spring-boot-admin/**' + - 'spring-boot-admin/metadata/*' + - 'spring-boot-admin/patches/*' - 'build.ps1' concurrency: diff --git a/.github/workflows/build_uaa_server.yaml b/.github/workflows/build_uaa_server.yaml index faff82c..eb7504a 100644 --- a/.github/workflows/build_uaa_server.yaml +++ b/.github/workflows/build_uaa_server.yaml @@ -2,18 +2,22 @@ name: Build UAA Server on: pull_request: - branches: - - main paths: - '.github/workflows/build_uaa_server.yaml' - - 'uaa-server/**' + - 'uaa-server/Dockerfile' + - 'uaa-server/metadata/*' + - 'uaa-server/*.yml' + - 'uaa-server/*.properties' - 'build.ps1' push: branches: - main paths: - '.github/workflows/build_uaa_server.yaml' - - 'uaa-server/**' + - 'uaa-server/Dockerfile' + - 'uaa-server/metadata/*' + - 'uaa-server/*.yml' + - 'uaa-server/*.properties' - 'build.ps1' concurrency: