WebSvc.DbProvisioner
1.0.2
dotnet add package WebSvc.DbProvisioner --version 1.0.2
NuGet\Install-Package WebSvc.DbProvisioner -Version 1.0.2
<PackageReference Include="WebSvc.DbProvisioner" Version="1.0.2" />
<PackageVersion Include="WebSvc.DbProvisioner" Version="1.0.2" />
<PackageReference Include="WebSvc.DbProvisioner" />
paket add WebSvc.DbProvisioner --version 1.0.2
#r "nuget: WebSvc.DbProvisioner, 1.0.2"
#:package WebSvc.DbProvisioner@1.0.2
#addin nuget:?package=WebSvc.DbProvisioner&version=1.0.2
#tool nuget:?package=WebSvc.DbProvisioner&version=1.0.2
WebSvc.DbProvisioner
Given a database name, create a real, ready-to-use MySQL or PostgreSQL database — plus a scoped
user and a working connection string — on Plesk, cPanel, or a bare/standalone server, behind one
interface. The counterpart to WebSvc.SqlConnector: that talks to a database that exists; this
makes one exist in the first place.
What it does — and deliberately doesn't
- Provisions a database "inside an existing thing" — a Plesk subscription, a cPanel account,
or a server an admin credential already reaches. It does not create the subscription/account
itself (Plesk's
webspace-idor the cPanel account must already exist). - Creates a scoped user with full read/write on that one database only — never a "universal" user with access to every database in the subscription/account.
- Deprovisions the reverse — removes the database (and, where the host's own API supports it,
the user) given the reference
ProvisionAsyncreturned. - Does not create tables.
ProvisionAsynchands back an empty database and working credentials; what schema goes inside it is entirely your own application's concern (an EF Core migration, a config-driven schema generator, plainCREATE TABLEstatements — connect with the returned credentials using any normal MySQL/PostgreSQL client, orWebSvc.SqlConnector, and do exactly what you'd do for a database that already existed). One narrow escape hatch exists for this:ProvisionDatabaseRequest.InitializationScript, raw SQL run once immediately after creation, if you supply it — currently implemented for the Standalone provider only (see below). - Does not store anything.
ProvisionAsync/DeprovisionAsyncnever write credentials or references anywhere on their own — where you persist the result (a database row, a secrets vault, nothing at all) is entirely your own application's decision.
Setup
builder.Services.AddDbProvisioner(builder.Configuration);
{
"DbProvisioner": {
"Plesk": {
"HostOrIp": "203.0.113.10",
"Port": 8443,
"SecretKey": "778a476a-cf1c-7434-ea47-9f229d70e934",
"DefaultWebspaceId": 142,
"DefaultConnectionHostOverride": "172.17.0.1"
},
"CPanel": {
"HostOrIp": "198.51.100.20",
"Username": "acmehost",
"ApiToken": "…"
},
"Standalone": {
"MySql": { "Host": "10.0.0.5", "Port": 3306, "AdminUsername": "root", "AdminPassword": "…" }
}
}
}
Using it
public class DatabaseController(IDatabaseProvisioningProviderRegistry providers)
{
public async Task<IActionResult> Provision(string tenant)
{
var provider = providers.Get("plesk")!;
var result = await provider.ProvisionAsync(new ProvisionDatabaseRequest
{
DatabaseName = $"{tenant}_app",
Engine = DatabaseEngine.MySql,
});
if (!result.Success) return Problem(result.Error);
// result.Database.ConnectionString / .Host / .Port / .Username / .Password
// are all real, immediately usable — persist result.Database.Reference
// yourself if you might ever need to call DeprovisionAsync later.
return Ok(result.Database);
}
}
Why the returned host might not be what the panel itself reports
Plesk and cPanel both report their own idea of the database server's address — commonly
localhost, since the panel and the database server usually live on the same box. From inside a
Docker container (default bridge networking), localhost resolves to the container itself, not
the host — that address is simply wrong for a containerized consumer, every time.
Every provider resolves the connection host in this order:
ProvisionDatabaseRequest.ConnectionHostOverride, if set — always wins.- The provider's own
DefaultConnectionHostOverrideoption, if set. - Otherwise, the host the panel's own API reported — unless that's a loopback address
(
localhost/127.0.0.1/::1), in which casehost.docker.internalis used instead of silently trusting a value that's almost never reachable from a container. On Linux, this requires the consuming container to be run with--add-host=host.docker.internal:host-gateway; it works out of the box on Docker Desktop.
Provider notes
Plesk — tries the REST API (POST /api/v2/databases) first only if RestApiKey is configured;
this path is best-effort, since the exact request/response shape wasn't confirmed against a live
server as of this release (Plesk's REST database coverage varies by version — check your own
server's live spec at https://<host>:8443/api/v2/openapi.yml, or in-panel under Tools &
Settings → Remote API (REST) → API Reference and Playground, and let us know if it differs from
what's implemented here). Falls back to the XML-RPC API (add-db/add-db-user/del-db/
del-db-user) otherwise or on any REST failure — fully implemented, verified against
docs.plesk.com, and works on every Plesk version checked.
cPanel — UAPI only, deliberately (WHM creates whole hosting accounts; UAPI acts inside one that
already exists — provisioning a new account is out of scope here). MySQL only in this release —
Postgres support in cPanel is far less commonly available and UAPI's Postgres module differs
enough to warrant its own pass. cPanel silently prefixes both the database and user name with your
account username ({account}_{name}) — ProvisionedDatabase.DatabaseName/.Username already
reflect the real, qualified names you connect with. Mysql::delete_user isn't independently
confirmed in cPanel's own UAPI docs (only create_user and delete_database are) — deprovisioning
removes the database and attempts the user removal best-effort, surfacing a warning rather than
failing outright if that specific call isn't supported on a given server.
Standalone — no external API, no control panel: connects directly with an admin MySQL/Postgres
login you already hold and runs the DDL itself. For PostgreSQL specifically, the new user is made
the owner of the new database (rather than only granted database-level privileges) — Postgres'
privilege model doesn't give a plain GRANT full control over objects created inside a database
the way MySQL's does, so ownership is the correct way to give "maximum permission on this one
database" here. The only provider InitializationScript currently runs against.
Not built yet, deliberately
- Aiven — assessed, a good fit for this same interface (
POST .../service/{name}/db), with one real caveat: Aiven's users are scoped to the whole service, not to one database, so a strict single-database restriction would need this package to additionallyREVOKE CONNECTon every other database in the service. - Supabase — assessed, not a clean fit for this interface. Supabase's API provisions a whole
new project (one Postgres per project — async, billable, region/plan-selected), not "a database
inside an existing thing." Better as its own
ProvisionProjectAsync-shaped concept later.
Adding either — or any future host that fits the "add to an existing thing" shape — is one new
class against IDatabaseProvisioningProvider, never a redesign.
What this package hasn't been tested against yet
Every provider here is built against verified, sourced API documentation (Plesk's XML-RPC API, cPanel's UAPI) and unit-tested for connection-string building, host resolution, and response parsing — but none of the three has been exercised against a real, live Plesk server, cPanel account, or MySQL/PostgreSQL instance yet (none was available in the environment this was built in). Verify against your own real infrastructure before depending on this in production, and please report back anything that doesn't match — especially Plesk's REST behavior, which is the one area explicitly built as best-effort rather than confirmed.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- MySqlConnector (>= 2.6.1)
- Npgsql (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.