Skip to content

refactor(control-plane): extract TenantInfraProvider interface (Epic A)#814

Closed
2witstudios wants to merge 1 commit intopu/epic-11-close-loopfrom
epic-a/infra-provider-abstraction
Closed

refactor(control-plane): extract TenantInfraProvider interface (Epic A)#814
2witstudios wants to merge 1 commit intopu/epic-11-close-loopfrom
epic-a/infra-provider-abstraction

Conversation

@2witstudios
Copy link
Copy Markdown
Owner

Summary

  • Introduces TenantInfraProvider interface — a 7-method contract (provision, destroy, suspend, resume, upgrade, healthCheck, exec) that decouples tenant lifecycle management from any specific infrastructure backend
  • Implements DockerComposeProvider — moves all Docker-specific logic (compose commands, health polling, env file writing) into a single provider module, extracting it from provisioning-engine and tenant-lifecycle
  • Refactors provisioning-engine and tenant-lifecycle to consume the provider via DI — reducing ProvisioningDeps from 9 fields to 5, and LifecycleDeps from 5 fields to 2
  • Renames schema column dockerProjectinfraId and adds provider column (default 'docker') to support future multi-provider coexistence
  • Adds INFRA_PROVIDER env var in the entrypoint for future provider selection (currently only docker is supported)

What changed

Area Before After
Docker coupling Spread across 3 files (~200 lines of compose commands) Isolated in DockerComposeProvider (124 lines)
provisioning-engine.ts Built docker compose commands, managed fs, called pollHealth Calls provider.provision(), provider.healthCheck(), provider.destroy()
tenant-lifecycle.ts 124 lines with composeCmd helper, execOrFail, APP_SERVICES 72 lines — just state machine + provider calls
Health polling Inline function in index.ts, threaded to engine + lifecycle Lives inside DockerComposeProvider.healthCheck()
DB URL extraction Wrote env to disk → read it back for POSTGRES_* vars Extracted from in-memory env content (no disk round-trip)
Schema docker_project column infra_id + provider columns

Files

New (4):

  • apps/control-plane/src/providers/types.tsTenantInfraProvider interface
  • apps/control-plane/src/providers/docker-compose-provider.ts — Docker Compose implementation
  • apps/control-plane/src/providers/index.ts — barrel exports
  • apps/control-plane/src/providers/__tests__/docker-compose-provider.test.ts — 16 tests

Modified (9):

  • services/provisioning-engine.ts — uses provider, removes fs/composePath/basePath/pollHealth deps
  • services/tenant-lifecycle.ts — uses provider, removes executor/composePath/basePath/pollHealth deps
  • index.ts — creates and wires DockerComposeProvider
  • schema/tenants.tsdockerProjectinfraId, added provider
  • 5 test files updated (fixtures + assertions)

Verification

  • 212/212 tests pass (including 16 new provider tests)
  • TypeScript cleanpnpm --filter control-plane typecheck passes
  • Net -358/+579 lines — more code due to new provider + tests, but the core services got simpler (engine -38, lifecycle -57 lines)
  • Zero behavior change — Docker Compose provisioning works identically

Why this matters

This is the foundation for the Firecracker migration path. Future providers (AWS Fargate, Fly Machines) implement the same TenantInfraProvider interface and get wired via the INFRA_PROVIDER env var. The existing Docker path continues to work for local dev and VPS deployments.

Test plan

  • All 212 existing control-plane tests pass
  • New DockerComposeProvider tests verify correct compose command construction
  • TypeScript compiles cleanly
  • Manual: deploy control-plane and verify Docker-based provisioning still works end-to-end

🤖 Generated with Claude Code

Introduce a provider abstraction layer that decouples tenant lifecycle
management from Docker Compose, enabling future infrastructure backends
(AWS Fargate, Fly Machines) without changing the orchestration logic.

New files:
- providers/types.ts — TenantInfraProvider interface (7 methods)
- providers/docker-compose-provider.ts — Docker Compose implementation
- providers/__tests__/docker-compose-provider.test.ts — 16 provider tests

Refactored:
- provisioning-engine.ts — uses provider.provision/healthCheck/destroy
  instead of raw docker compose commands; extracts DB URL from in-memory
  env content instead of re-reading from disk
- tenant-lifecycle.ts — uses provider.suspend/resume/upgrade/destroy
  instead of composing shell commands; deps reduced from 5 to 2
- index.ts — wires DockerComposeProvider via DI, supports INFRA_PROVIDER
  env var for future provider selection

Schema:
- Renamed dockerProject → infraId column
- Added provider column (varchar, default 'docker')

Zero behavior change — pure refactor. 212/212 tests pass, clean typecheck.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7624cc86-c025-4e41-bc63-30a13a145920

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch epic-a/infra-provider-abstraction

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: f8265149e0

ℹ️ 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 on lines +28 to +29
infraId: varchar('infra_id', { length: 255 }),
provider: varchar('provider', { length: 50 }).notNull().default('docker'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add a migration for the tenant infra columns

tenants now maps infra_id and provider, but the checked-in Drizzle artifacts still create/select the old shape: apps/control-plane/drizzle/0000_lovely_slayback.sql:37-54 defines docker_project only, and apps/control-plane/drizzle/meta/0000_snapshot.json:232-237 still snapshots that column. After pnpm --filter control-plane db:migrate, both fresh and upgraded databases will be missing infra_id/provider, so repository calls like select().from(tenants) will start failing with missing-column errors at runtime.

Useful? React with 👍 / 👎.

Comment on lines +87 to +88
await provider.provision(request.slug, envContent)
infraStarted = true
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Trigger cleanup when provider.provision fails partway

The new infraStarted flag is only set after provider.provision() returns successfully. If the Docker provider has already written .env and docker compose up -d creates some resources before exiting non-zero, the catch block will skip provider.destroy() and leave partially created containers/volumes behind. Before this refactor, composeStarted was flipped before throwing on a failed compose-up specifically to run teardown in that scenario.

Useful? React with 👍 / 👎.

@2witstudios 2witstudios deleted the branch pu/epic-11-close-loop April 9, 2026 20:07
@2witstudios 2witstudios closed this Apr 9, 2026
@2witstudios 2witstudios deleted the epic-a/infra-provider-abstraction branch April 16, 2026 03:23
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.

1 participant