Skip to content

feat(providers): general ?layout=flat setting to resolve at the store root - #183

Closed
ojhermann wants to merge 1 commit into
cachix:mainfrom
ojhermann:feat/infisical-flat-layout
Closed

feat(providers): general ?layout=flat setting to resolve at the store root#183
ojhermann wants to merge 1 commit into
cachix:mainfrom
ojhermann:feat/infisical-flat-layout

Conversation

@ojhermann

@ojhermann ojhermann commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Resolves #181. This started as an Infisical-only ?layout=flat (raised by @nickdichev migrating from BWS); per @domenkozar's suggestion it is now a general provider setting, since the namespaced-vs-root question is the same for every hierarchical backend.

What this adds

?layout=flat maps SecretSpec's {project}/{profile}/{key} convention onto the store root — the secret is addressed by key alone, dropping the secretspec/{project}/{profile} scaffolding but keeping any provider-native container (a Vault mount) or user prefix (an AWS ?prefix=, an Infisical ?path=). The default nested layout is unchanged, so existing configs behave exactly as before.

Honored by Infisical, Vault, OpenBao, AWS Secrets Manager, Google Cloud Secret Manager, and Azure Key Vault. Providers with no hierarchy (dotenv, env, bws) are already flat and ignore it.

[providers]
# Address secrets by key alone at the store root
vault  = "vault://vault.example.com:8200/secret?layout=flat"
awssm  = "awssm://us-east-1?prefix=myteam&layout=flat"   # -> myteam/DATABASE_URL
infisical = "infisical://app.infisical.com/7e2f1a4c-...?layout=flat"

Design

  • Parsed once in a shared ProviderUrl::layout() (strict: nested/flat; an unknown value errors, like ?tls=). Each provider's convention_address then honors it in its own dialect. Layout is per-provider-instance, so it works correctly in per-secret fallback chains (a flat provider alongside a nested one).
  • Vault and OpenBao pick it up through the shared KvConfig/KvProvider core added in feat(openbao): add first-class OpenBao provider #187, so each product carries it under its own scheme with no duplicated logic.
  • Flat addresses by key alone, so project/profile are not validated. Where the backend constrains names, the key must fit: Azure Key Vault uses the key verbatim and refuses an underscore that the nested layout would have Base32-encoded away.
  • Profile separation. Dropping the profile means profiles no longer separate by name; a provider that pins its environment another way (an Infisical ?env=) still keeps them apart, but flat plus a pinned env collapses every profile onto one key — a deliberate, documented "you're giving up profile separation" mode.

The two questions this PR previously left open

Both are now decided, so this is a concrete proposal rather than a fork in the road. Reasoning below in case you'd steer either differently.

Mechanism: a URI query param

Chosen: ?layout= in the provider URI. The deciding argument is per-secret fallback chains — providers = ["flat_vault", "keyring"] puts two provider instances side by side, and layout has to be describable per instance to express that. It also needs no answer to "what configures an inline --provider vault://... on the CLI", since the URI is the only thing such an invocation has.

Considered and deliberately not built: a layout field on the provider alias. ProviderAlias already takes a table form ({ uri = "...", credentials = { ... } }), so layout = "flat" would fit structurally, and it would be more discoverable and more typo-checkable than a query string. What stopped me is that it buys no expressive power the URI doesn't already have — the alias is written once either way — while adding a second way to say the same thing, a precedence rule, and a disagreement case (uri = "...?layout=flat" next to layout = "nested" — silent precedence, or a load error?).

The larger question underneath, which is the one I'd actually value your steer on: should provider settings live in the alias table at all? If they should, the coherent move is to lift ?layout=, ?kv=, ?auth=, ?prefix= there together as a design, not to special-case one setting first — and layout is the least representative example to prejudge it with. Happy to go either way; I just didn't want to answer that question by accident.

Spelling: ?layout=flat / ?layout=nested

Chosen: an enum, matching the existing enum-valued ?kv= and ?auth= style, with unknown values rejected rather than guessed.

Considered: ?flat=true — shorter and reads more naturally, but it's a boolean where the concept is really a choice of naming scheme. A third layout later (say, one that drops the profile but keeps the project) would need a new parameter and an awkward interaction with this one, where ?layout= just gains a variant.

Tests & docs

Per-provider unit tests (no HTTP mocks): flat addressing, unconstrained project/profile names, key-still-validated, URI round-trip, unknown-layout rejected. Full crate suite green (588 tests); fmt/clippy clean. Docs updated across the providers reference (a general Layout section) and each provider page — including the new OpenBao guide — labeled 0.17+.

Rebased on current main, after #187 landed.

🤖 Generated with Claude Code

@ojhermann
ojhermann force-pushed the feat/infisical-flat-layout branch from d070f6e to 56bae9a Compare July 20, 2026 21:11
@ojhermann ojhermann changed the title feat(infisical): add ?layout=flat to resolve at the environment root feat(providers): general ?layout=flat setting to resolve at the store root Jul 20, 2026
… root

Maps SecretSpec's {project}/{profile}/{key} convention onto the store root:
the secret is addressed by key alone, dropping the secretspec/{project}/{profile}
scaffolding but keeping any provider-native container (a Vault mount) or user
prefix (an AWS ?prefix=, an Infisical ?path=). The default nested layout is
unchanged.

Honored by every hierarchical provider -- Infisical, Vault, OpenBao, AWS Secrets
Manager, Google Cloud Secret Manager and Azure Key Vault. Providers with no
hierarchy (dotenv, env, bws) are already flat and ignore it.

Layout is a shared, per-provider-instance setting: parsed once through a strict
ProviderUrl::layout() (nested/flat, unknown value errors), then honored by each
provider's convention_address in its own dialect, so it works correctly in
fallback chains. Vault and OpenBao pick it up through their shared KV core, so
each product's URI carries it under its own scheme. Flat addresses by key alone,
so project/profile aren't validated; where the backend constrains names the key
must fit, and Azure Key Vault -- which uses the key verbatim -- refuses an
underscore the nested layout would have Base32-encoded away. Dropping the
profile gives up profile separation unless the provider pins its environment
another way (an Infisical ?env=); flat plus such a pin collapses every profile
onto one key deliberately.

Resolves #181.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ojhermann
ojhermann force-pushed the feat/infisical-flat-layout branch from 56bae9a to 39ba748 Compare July 22, 2026 16:14
@ojhermann
ojhermann marked this pull request as ready for review July 22, 2026 16:14
@ojhermann ojhermann closed this by deleting the head repository Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Infisical provider: ?layout=flat opt-in to resolve from the environment root (no {project}/{profile} folders)

1 participant