Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / cmd/register-tenant/main.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | cmd/register-tenant/main.go |
| Original SHA-256 | 5e0a49142c3c3f855d1b14f573c2a5f12051fe8d688c2c0d5bf4f1ba6a6a9695 |
| Git blob SHA | 698a7f87f6b7b19fa064d183165ee26bd188a949 |
| Reading-copy SHA-256 | 5e0a49142c3c3f855d1b14f573c2a5f12051fe8d688c2c0d5bf4f1ba6a6a9695 |
Recovered source transcript
Download complete source text ↓126 lines · 5.2 KB
// Command register-tenant publishes an EXISTING database into the control plane tenant directory, so
// the router can resolve an organization to it.
//
// It is the counterpart to provision-tenant and deliberately separate from it. provision-tenant creates
// a brand new, empty, schema identical database for a new client. register-tenant adopts a database that
// already exists and already holds data, which is the case that matters when an established deployment
// moves from the shared model to the per tenant model: the original database keeps serving exactly the
// data it already serves, and the only thing that changes is that the router now knows where it is.
// Nothing is copied, moved, or dropped.
//
// It writes only the directory row. It does not touch the tenant database at all, so registering is
// non destructive and reversible by setting the status back to offline.
//
// The credential is NOT passed here and is never stored in the directory. Only a logical reference is
// recorded (CLAUDE.md rule 3), which internal/platform/secrets resolves at pool open time from the root
// owned environment file, so a dump of the directory is not a dump of every tenant credential.
//
// Usage:
//
// register-tenant -control-dsn postgres://missionmatch_migrator:...@host:5433/missionmatch \
// -org 11111111-1111-1111-1111-111111111111 \
// -db-name missionmatch -host 100.94.8.94 -port 5432 \
// -secret-ref tenant_acme -status active
//
// The DSN the reference resolves to must then be present in the API environment as
// MM_TENANT_DSN_<REF>, upper cased with non alphanumeric characters folded to underscore.
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/missionworkforce/missionmatch/internal/platform/database"
"github.com/missionworkforce/missionmatch/internal/platform/directory"
)
func main() {
controlDSN := flag.String("control-dsn", "", "migrator DSN to the CONTROL PLANE database (the one holding tenant_directory), defaults to MM_MIGRATOR_DATABASE_URL")
org := flag.String("org", "", "organization uuid this database serves")
dbName := flag.String("db-name", "", "the tenant database name")
host := flag.String("host", "", "host the tenant database listens on (a stable address, not a NAT address that changes on restart)")
port := flag.Int("port", 5432, "port the tenant database listens on")
secretRef := flag.String("secret-ref", "", "logical credential reference, resolved at pool open time (never a password)")
status := flag.String("status", string(directory.StatusActive), "provisioning|active|suspended|migrating|offline")
cloud := flag.String("cloud", "local", "local|aws_govcloud|azure_gov")
region := flag.String("region", "", "cloud region, empty for local")
flag.Parse()
if *controlDSN == "" {
*controlDSN = os.Getenv("MM_MIGRATOR_DATABASE_URL")
}
switch {
case *controlDSN == "":
log.Fatal("register-tenant: -control-dsn (or MM_MIGRATOR_DATABASE_URL) is required")
case *org == "":
log.Fatal("register-tenant: -org is required")
case *dbName == "":
log.Fatal("register-tenant: -db-name is required")
case *host == "":
log.Fatal("register-tenant: -host is required")
case *secretRef == "":
log.Fatal("register-tenant: -secret-ref is required")
}
orgID, err := uuid.Parse(*org)
if err != nil {
log.Fatalf("register-tenant: -org %q is not a uuid: %v", *org, err)
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, *controlDSN)
if err != nil {
log.Fatalf("register-tenant: control plane pool: %v", err)
}
defer pool.Close()
// Writing the directory is a migrator privilege, and pointing this at the app role would fail
// confusingly on the INSERT rather than up front. Assert the role so the failure names the cause.
if err := database.AssertMigratorRole(ctx, pool); err != nil {
log.Fatalf("register-tenant: control plane role check: %v", err)
}
dir := directory.New(pool)
if err := dir.Register(ctx, directory.TenantConn{
OrganizationID: orgID,
DBName: *dbName,
Host: *host,
Port: *port,
SecretRef: *secretRef,
Status: directory.Status(*status),
Cloud: *cloud,
Region: *region,
}); err != nil {
log.Fatalf("register-tenant: %v", err)
}
// Read it back rather than trusting the write, and report what the router will actually resolve.
tc, err := dir.Lookup(ctx, orgID)
if err != nil {
log.Fatalf("register-tenant: registered but could not read back: %v", err)
}
log.Printf("register-tenant: org %s resolves to %s@%s:%d status=%s cloud=%s",
tc.OrganizationID, tc.DBName, tc.Host, tc.Port, tc.Status, tc.Cloud)
log.Printf("register-tenant: the API must have %s set to that database's app role DSN", envVarFor(tc.SecretRef))
}
// envVarFor mirrors the env backend's reference folding in internal/platform/secrets, so the operator is
// told the exact variable name to set rather than having to derive it.
func envVarFor(ref string) string {
var b strings.Builder
for _, r := range strings.ToUpper(strings.TrimSpace(ref)) {
if (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
b.WriteRune(r)
} else {
b.WriteByte('_')
}
}
return fmt.Sprintf("MM_TENANT_DSN_%s", b.String())
}