ammachado opened a new pull request, #1917:
URL: https://github.com/apache/camel-spring-boot/pull/1917

   ## What this does
   
   Seven vault and secrets starters each carried a near-identical 
`ApplicationListener<ApplicationEnvironmentPreparedEvent>` that resolved 
`{{<prefix>:...}}` placeholders before the `ApplicationContext` exists. The 
bodies were roughly 108 lines each and differed only in the guard property, the 
override property source name, and how the `PropertiesFunction` is constructed.
   
   This extracts the shared lifecycle into 
`AbstractEarlyResolutionPropertiesParser` in `core/camel-spring-boot` and 
reduces each starter to three small overrides.
   
   | Starter | Listener | Guard property |
   | --- | --- | --- |
   | camel-aws-secrets-manager | `SpringBootAwsSecretsManagerPropertiesParser` 
| `camel.component.aws-secrets-manager.early-resolve-properties` |
   | camel-azure-key-vault | `SpringBootAzureKeyVaultPropertiesParser` | 
`camel.component.azure-key-vault.early-resolve-properties` |
   | camel-cyberark-vault | `SpringBootCyberArkVaultPropertiesParser` | 
`camel.component.cyberark-vault.early-resolve-properties` |
   | camel-google-secret-manager | 
`SpringBootGoogleSecretManagerPropertiesParser` | 
`camel.component.google-secret-manager.early-resolve-properties` |
   | camel-hashicorp-vault | `SpringBootHashicorpVaultPropertiesParser` | 
`camel.component.hashicorp-vault.early-resolve-properties` |
   | camel-ibm-secrets-manager | `IBMSecretsManagerVaultPropertiesParser` | 
`camel.component.ibm-secrets-manager.early-resolve-properties` |
   | camel-spring-cloud-config | `SpringBootCloudConfigPropertiesParser` | 
`camel.component.spring-cloud-config.early-resolve-properties` |
   
   The base class declares three abstract methods 
(`getEarlyResolutionProperty()`, `getOverridePropertySourceName()`, 
`createPropertiesFunction(ConfigurableEnvironment)`), one overridable 
`getSourceDescription()`, and a `final` `onApplicationEvent`.
   
   Every override property source name is preserved byte for byte, including 
`camel-ibm-secrets-manager-starter`'s asymmetric 
`overridden-ibm-secrets-manager-properties`, which lacks the `camel-` segment 
the other six use. That asymmetry is pre-existing and renaming it would break 
anyone looking that source up by name.
   
   ## Behaviour changes
   
   Most of this is a pure extraction, but three things do change. They are 
called out here so they can be reviewed as decisions rather than discovered as 
surprises.
   
   **1. Exception normalization.** Two starters previously threw exception 
types that signal a programming defect rather than an operator configuration 
error, and that slip past any `catch (RuntimeCamelException)` handler.
   
   | Path | Before | After |
   | --- | --- | --- |
   | Google client creation | `RuntimeException` wrapping `IOException` | 
`RuntimeCamelException`, cause chained |
   | Hashicorp missing token, host, port, scheme | `NullPointerException` via 
`Objects.requireNonNull` | `RuntimeCamelException`, original message plus the 
property key |
   | Hashicorp non-numeric port | `NumberFormatException` naming no property | 
`RuntimeCamelException` naming `camel.vault.hashicorp.port`, cause chained |
   
   No message became less specific. `Objects.requireNonNull`'s messages were 
already good (for example "Hashicorp Vault token is required") and they survive 
verbatim with the property key appended. The port case is the one that was 
genuinely unhelpful before, since `Integer.parseInt` reports `For input string: 
"..."` without ever naming the property the operator got wrong.
   
   **2. Blank values are now rejected for the four Hashicorp settings.** The 
new `required()` helper uses `ObjectHelper.isEmpty`, so 
`camel.vault.hashicorp.port=` now fails with "port is required (set 
camel.vault.hashicorp.port)" instead of reaching `Integer.parseInt` and dying 
on an empty string. An empty scheme now fails in the parser rather than deeper 
inside `VaultEndpoint`.
   
   **3. Placeholder unwrapping is now exact.** The old code did 
`value.replace("{{aws:", "").replace("}}", "")`, a global replace that would 
corrupt a secret path legitimately containing `}}`. The shared version uses 
`substring` against the known delimiter lengths. There is a test pinning this: 
`{{test:a}}b}}` must yield the remainder `a}}b`, which the old code turned into 
the empty string.
   
   A related simplification: the placeholder prefix is no longer hardcoded per 
starter. `PropertiesFunction.getName()` already returns exactly the prefix 
token (`aws`, `azure`, `gcp`, and so on), so the base class derives `"{{" + 
fn.getName() + ":"` itself. The prefix can no longer drift out of sync with the 
function that resolves it.
   
   ## Deliberately out of scope
   
   - **Property source precedence (CAMEL-24532).** Iteration order and 
last-write-wins semantics are preserved exactly. That defect is tracked 
separately and assigned to another contributor.
   - **Concatenated placeholders.** A value such as 
`uri={{aws:user}}:{{aws:pass}}` satisfies both `startsWith` and `endsWith`, so 
it is unwrapped into a garbage remainder and fails. This behaviour is unchanged 
from before this PR and from before #1900, so it is not a regression, but the 
class javadoc now documents the limitation instead of claiming such values are 
left alone. Worth its own ticket.
   - **Environment variables and relaxed binding.** 
`SystemEnvironmentPropertySource` extends `MapPropertySource`, so a placeholder 
in `MY_SECRET` is resolved and stored under the literal key `MY_SECRET`, while 
Spring's relaxed binding then resolves `my.secret` from the environment source 
rather than the exact-match override source. The unresolved placeholder stays 
effective with no error. This arrives from #1900 unchanged by this refactor and 
fixing it means changing property source semantics. Flagging it because it is 
security relevant and deserves its own ticket.
   
   ## Testing
   
   `mvn -o verify` passes in all 8 affected modules with zero failures and zero 
errors.
   
   - `core/camel-spring-boot`: 156 tests plus 2 integration tests. 11 of those 
are new, covering the shared class: the guard, flag-read ordering, whole-value 
matching, `OriginTrackedValue` unwrapping, exact delimiter stripping, failure 
aggregation with causes attached via `addSuppressed`, no override source 
registered on failure, tolerant mode, and a null resolution result.
   - Each starter gains a contract test asserting its guard property and 
override property source name as exact literals. These seven tests are 
intentionally near-identical rather than factored into a shared helper: a wrong 
guard key fails **open**, meaning early resolution silently never runs and the 
placeholder stays in the property value as the effective secret. A 
parameterized helper would compare a typo against itself.
   - `camel-hashicorp-vault-starter`'s `EarlyResolvedPropertiesTest` ran end to 
end against a real Testcontainers Vault and passed.
   
   Two coverage gaps worth stating plainly rather than leaving to be found:
   
   - The Google `IOException` to `RuntimeCamelException` change has no test. 
Forcing `SecretManagerServiceClient.create` to throw requires credential 
environment manipulation, and the alternatives drag live GCP into a unit test. 
Verified by inspection.
   - The remaining `EarlyResolvedPropertiesTest` classes are guarded by 
`@EnabledIfSystemProperty` and `@EnabledIfEnvironmentVariable` and skip without 
live cloud credentials. That is pre-existing. The behavioural safety net for 
this refactor is the shared class's own test suite in `core/camel-spring-boot`, 
which runs everywhere.
   
   No new dependencies, no POM changes, and therefore no regenerated code. 16 
files changed.
   
   ## Note on sequencing
   
   This originally stacked on #1900. That PR has since merged, and this branch 
has been rebased directly onto `main`, so it no longer depends on anything 
unmerged.
   
   ---
   
   _Claude Code on behalf of Adriano Machado._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to