Skip to content

feat(config): multi-deployment environments + ResolveDatabaseTargets#213

Open
Kiran01bm wants to merge 3 commits into
mainfrom
kiran01bm/multi-deployment-config-resolver
Open

feat(config): multi-deployment environments + ResolveDatabaseTargets#213
Kiran01bm wants to merge 3 commits into
mainfrom
kiran01bm/multi-deployment-config-resolver

Conversation

@Kiran01bm
Copy link
Copy Markdown
Collaborator

@Kiran01bm Kiran01bm commented Jun 2, 2026

What ?

Adds an additive deployments: map under databases.<db>.environments.<env> so one environment can fan out to N Tern deployments, plus a new ResolveDatabaseTargets resolver. No production behavior changes yet — no caller of ResolveDatabaseTargets exists (yet).

Why ?

The resolver shape is the contract between config and orchestration. Landing it before the apply write path is updated means downstream callers can be pure consumers of the new API — no half-migrated state in either direction.

Config shape

databases:
  payments:
    type: mysql
    environments:
      production:
        deployments:
          payments-a:
            target: payments
          payments-b:
            target: payments
          payments-c:
            target: payments

tern_deployments:
  payments-a:
    production: "tern-payments-a:9090"
  payments-b:
    production: "tern-payments-b:9090"
  payments-c:
    production: "tern-payments-c:9090"

The map key IS the Tern deployment name — same key space as tern_deployments, and the same value that will eventually land on apply_operations.deployment. Each entry just carries target:.

Resolver API

// New — returns one element per configured deployment, sorted by deployment key.
func (c *ServerConfig) ResolveDatabaseTargets(database, env string) ([]ResolvedDatabaseTarget, error)

// Existing — now delegates to ResolveDatabaseTargets. Errors with a clear
// "use ResolveDatabaseTargets" message when the env resolves to N != 1
// deployments. Single-deployment callers (plan/apply/progress handlers,
// integration tests) are unaffected.
func (c *ServerConfig) ResolveDatabaseTarget(database, env string) (ResolvedDatabaseTarget, error)

Validation rules

ServerConfig.Validate() now also rejects:

  • mixing scalar target / deployment with the deployments: map
  • mixing local dsn / dsn_from with the deployments: map
  • an empty deployments: map
  • a map entry with empty target
  • a map key not present in tern_deployments, or present but missing an endpoint for the current environment (same cross-check the scalar shape already does)

Back-compat matrix

Config has ResolveDatabaseTargets ResolveDatabaseTarget (legacy)
Scalar target + deployment 1-element slice unchanged, works
deployments: map (N entries) N elements sorted by deployment key errors with "use ResolveDatabaseTargets"
Local dsn / dsn_from 1-element slice (Deployment == Target == <db name>) unchanged, works
Both scalar and map present validation error at config load
Local DSN AND any remote routing validation error at config load
Neither scalar nor map nor local DSN validation error at config load

Design decision worth flagging

The map key is the Tern deployment name, not a separate "logical deployment label". An earlier draft had { deployment: payments-a, target: payments } inside each entry, which would split deployment identity across two keys for no current gain. If we ever need a logical label decoupled from the Tern endpoint name (e.g., for PR-comment display), we can add it later as an additive field on DeploymentTarget.

Cross-deployment promotion order (a deployment_order: analogous to environment_order:) is deferred to a follow-up that also renames the scheduler to operator. This PR sorts by deployment key for deterministic resolution.

Tests

  • TestServerConfig_ResolveDatabaseTargets: local DSN, scalar remote, sorted multi-deployment, unknown db, unknown env, multi-deployment via the legacy resolver, scalar via the legacy resolver.
  • TestServerConfig_DeploymentsMapValidation (table): happy path + six rejection cases (mixed scalar+map, empty map, empty target, unknown deployment, deployment without endpoint for env, local DSN + map).

Verification

  • go build ./...
  • go vet ./...
  • go test ./pkg/api/... ./pkg/state/... ./pkg/storage/... ./pkg/schema/... ./pkg/webhook/...

Out of scope

  • Wiring ResolveDatabaseTargets into the apply / plan write paths — a follow-up will start writing one apply_operations row per resolved deployment.
  • Server-owned cross-deployment promotion order (deployment_order:) — deferred to the follow-up that renames scheduler to operator.
  • Per-deployment rows in PR progress comments.
  • Per-deployment Check Run rollup.

Rollback

Pure revert. No on-disk artifact, no schema changes, no live callers of the new API.

Copilot AI review requested due to automatic review settings June 2, 2026 04:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class config + resolver support for “multi-deployment environments”, allowing a single (database, environment) to resolve to N Tern deployments deterministically, while keeping legacy single-deployment callers working (and explicitly erroring when they encounter N≠1).

Changes:

  • Extend EnvironmentConfig with an additive deployments: map and validation rules to prevent mixing local DSN, scalar routing, and map routing.
  • Introduce ServerConfig.ResolveDatabaseTargets() and refactor ResolveDatabaseTarget() to delegate and error when multiple deployments resolve.
  • Document the new config shape and add Helm values examples; add unit tests covering resolution and validation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
pkg/api/config.go Adds Deployments config shape, validates it, and implements ResolveDatabaseTargets + legacy delegation behavior.
pkg/api/config_test.go Adds tests for multi-deployment resolution ordering and validation rejection cases.
docs/configuration.md Documents the multi-deployment environment YAML shape and rules.
charts/schemabot/values.yaml Adds a commented Helm values example showing multi-deployment configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/api/config.go Outdated
@Kiran01bm Kiran01bm changed the title feat(config): multi-deployment environments + ResolveDatabaseTargets (MD-3) feat(config): multi-deployment environments + ResolveDatabaseTargets Jun 2, 2026
Add an optional `deployments:` map under `databases.<db>.environments.<env>`
so one environment can fan out to N Tern deployments. The map key IS the
Tern deployment name (same key space as `tern_deployments`, and the same
value that will eventually land on the per-deployment apply row); each
entry just carries `target:`. Mutually exclusive with the scalar
`target`/`deployment` pair and with `dsn`/`dsn_from`.

Resolver changes:
- New `ResolveDatabaseTargets(database, env) ([]ResolvedDatabaseTarget, error)`
  returns one element per configured deployment, sorted by deployment key
  for deterministic ordering. Scalar remote and local DSN configs still
  return a single-element slice.
- `ResolveDatabaseTarget` now delegates to `ResolveDatabaseTargets` and
  errors with a clear "use ResolveDatabaseTargets" message when the env
  resolves to more than one deployment. Existing single-deployment
  callers (plan/apply/progress handlers, integration tests) are
  unaffected.

Validation additions in `ServerConfig.Validate()`:
- reject mixing scalar `target`/`deployment` and the `deployments:` map
- reject mixing local `dsn`/`dsn_from` and the `deployments:` map
- reject an empty `deployments:` map
- reject map entries with empty `target`
- require each map key to exist in `tern_deployments` with an endpoint
  configured for the current environment (same cross-check as the
  scalar shape)

Tests:
- TestServerConfig_ResolveDatabaseTargets — local, scalar, sorted
  multi-deployment, unknown db, unknown env, multi-deployment via the
  legacy resolver, scalar via the legacy resolver.
- TestServerConfig_DeploymentsMapValidation — happy path + six
  rejection cases.

Docs:
- docs/configuration.md gains a "Multi-Deployment Environment" section
  with a worked example and the validation rules.
- charts/schemabot/values.yaml gains a commented multi-deployment
  example next to the existing local/gRPC examples.

No production behavior changes yet — no caller of `ResolveDatabaseTargets`
exists.
@Kiran01bm Kiran01bm force-pushed the kiran01bm/multi-deployment-config-resolver branch from 5f08dbc to 8ae60e6 Compare June 2, 2026 04:11
ResolveDatabaseTargets used len(envConfig.Deployments) > 0 to decide
whether to take the map branch, so an explicitly configured but empty
'deployments: {}' fell through to scalar routing and returned the
misleading 'missing server-side target/deployment' error. Validate()
already catches this at config load, but the resolver should also
return a coherent error for callers that construct ServerConfig
directly (tests, hot-reload paths).

Switch the branch guard to '!= nil' and add explicit checks for
empty map and empty map keys, matching the rules already enforced
by Validate(). Adds TestServerConfig_ResolveDatabaseTargets_BypassValidate
covering all three cases.
@Kiran01bm Kiran01bm marked this pull request as ready for review June 2, 2026 04:59
@Kiran01bm Kiran01bm requested review from aparajon and morgo as code owners June 2, 2026 04:59
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63d84fe79c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/api/config.go
Validate() now rejects  maps with >1 entries with a clear
error. Single-entry maps still pass (behaviourally identical to scalar
target/deployment). Docs and Helm example marked as PREVIEW. The
ResolveDatabaseTargets resolver and types are unchanged, ready for the
follow-up that wires plan/apply.
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.

3 participants