Rustango docs
← Guides

Référence CLI manage

Ceci est l'outil en ligne de commande de Rustango, comparable à artisan de Laravel ou à la commande rails de Rails. Dans un projet généré via cargo rustango new, un seul binaire exécute chaque commande (« verbe ») :

cargo run                          # runserver (no args = boot the HTTP server)
cargo run -- migrate               # any other verb
cargo run -- --help                # full subcommand list

One binary runs every manage verb — server, migrations, scaffolders, database utilities, and system commands

Source : rustango::manage (Cli, le répartiteur de verbes) — derrière la fonctionnalité manage (activée par défaut).

Version exécutable : chaque verbe présenté ici s'exécute dans un projet généré ; l'exemple getting_started_blog est piloté par cargo run -- migrate et consorts.

Nouveau terme rencontré ici ? scaffold (générateur de squelette), migration, tenant (locataire) — voir le glossaire.

Le routeur de commandes se trouve dans rustango::manage::Cli ; votre src/main.rs le branche ainsi :

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    rustango::manage::Cli::new().api(urls::api()).run().await
}

Les projets multi-tenant ajoutent .tenancy() à la chaîne. Cela fait basculer le routeur vers rustango::tenancy::manage et débloque les commandes multi-tenant.

Forme plus ancienne — les projets générés par manage startapp --with-manage-bin (ou datant d'avant la v0.16) livrent encore un src/bin/manage.rs. Ceux-ci utilisent cargo run --bin manage -- <verb>. Les deux formes acceptent les mêmes verbes.

Chaque commande affiche sa sortie sur stdout et se termine avec un code de retour non nul en cas d'erreur de validation ou d'E/S. Exécutez cargo run -- --help (ou <verb> --help) pour l'aide intégrée.


Table des matières


Migrations

makemigrations [name]

Génère un fichier de migration à partir des changements de vos modèles. Elle compare vos modèles enregistrés au dernier instantané de schéma sauvegardé dans migrations/ et écrit un nouveau fichier JSON avec ce qui a changé.

cargo run -- makemigrations                          # auto-name (e.g. 0004_add_slug_to_posts)
cargo run -- makemigrations rename_status_to_state   # custom suffix

Changements détectés automatiquement :

  • CreateTable / DropTable
  • AddColumn / DropColumn
  • AlterColumnType / AlterColumnNullable / AlterColumnDefault / AlterColumnMaxLength
  • AlterColumnUnique
  • CreateIndex / DropIndex
  • AddCheckConstraint / DropCheckConstraint
  • CreateM2MTable / DropM2MTable

NON détectés automatiquement (renommage vs suppression+ajout est ambigu) :

  • RenameTable, RenameColumn — utilisez --empty et modifiez le JSON.

makemigrations --app <app>

Limite la migration à une seule app. Elle écrit dans le répertoire migrations/ propre à cette app, sous <project_root>/<app>/migrations/, et ne regarde que les modèles appartenant à cette app.

cargo run -- makemigrations --app blog
cargo run -- makemigrations --app blog backfill_slugs

makemigrations --scope <registry|tenant>

Réservé au multi-tenant. Écrit une seule migration pour uniquement les modèles d'un scope donné — ceux dont l'attribut #[rustango(scope = "...")] correspond. (Les tables « registry » sont partagées entre tous les tenants ; les tables « tenant » vivent par tenant.) Sans ce drapeau, un simple makemigrations dans un projet de tenancy scinde automatiquement les changements en DEUX fichiers — un pour les modèles registry, un pour les modèles tenant — afin que les tables partagées du framework (Org, Operator) ne se retrouvent pas dans les migrations par tenant exécutées par migrate-tenants.

cargo run -- makemigrations                       # tenancy: writes 0NN_<auto>.json (registry) + 0MM_<auto>.json (tenant) as needed
cargo run -- makemigrations --scope tenant        # explicit single-scope diff
cargo run -- makemigrations --scope registry      # explicit single-scope diff

Pourquoi cette scission compte : avant la v0.24.2, un simple makemigrations sur un projet de tenancy regroupait les opérations sur rustango_operators (une table registry) dans une migration tenant. Lorsque migrate-tenants exécutait ce fichier, rustango_operators se résolvait via search_path vers la copie registry et entrait en conflit avec la contrainte déjà présente là-bas.

makemigrations --empty <name>

Crée une migration vide (sans opérations forward) que vous remplissez vous-même à la main. Utilisez-la lorsque vous devez écrire des opérations de données ou de renommage que le détecteur automatique ne peut pas générer. Modifiez le JSON résultant vous-même.

cargo run -- makemigrations --empty rename_status_to_state
# Then edit migrations/0005_rename_status_to_state.json:
#   "forward": [
#     {"schema": {"RenameColumn": {"table": "posts", "old_column": "status", "new_column": "state"}}}
#   ]

makemigrations --merge

Corrige un historique de migrations qui s'est scindé en deux branches (issue #346). Cela se produit quand deux personnes exécutent chacune makemigrations sur leur propre branche de fonctionnalité, de sorte que les deux nouveaux fichiers pointent vers le même parent. Une fois les deux branches fusionnées, l'historique compte deux « feuilles » (points de fin), et le prochain makemigrations en choisirait une arbitrairement comme parent.

--merge détecte cette situation et écrit un NNNN_merge.json vide dont le parent pointe vers la dernière feuille par ordre alphabétique, réunissant l'historique en une seule chaîne. Son instantané de schéma reflète l'état combiné, lu depuis le registre de modèles en direct — les modèles des deux branches sont compilés à ce stade, donc l'instantané est exact.

cargo run -- makemigrations --merge
# wrote migrations/0004_merge.json
#     merge node — empty `forward`, anchors the chain after divergent leaves
  • Déjà une seule chaîne → affiche no merge needed et se termine proprement. Sûr à exécuter sur un historique sain.
  • Historiques vraiment séparés (pas une collision de branches) → échoue au lieu d'inventer un parent.
  • Ne peut pas être combiné avec --empty, --app, --scope, ou un nom positionnel.

migrate

Applique toutes les migrations en attente à la base de données, dans l'ordre — l'équivalent du php artisan migrate de Laravel. C'est la commande à exécuter après makemigrations pour changer réellement votre schéma.

cargo run -- migrate
cargo run -- migrate --dry-run                       # print SQL without writing

Chaque fichier s'exécute dans une transaction par défaut, donc un échec annule tout le fichier. Réglez "atomic": false dans le JSON pour désactiver ce comportement — nécessaire pour des instructions comme CREATE INDEX CONCURRENTLY qui ne peuvent pas s'exécuter dans une transaction.

En mode tenancy (Cli::tenancy()), migrate connaît le scope : elle applique d'abord les migrations registry à la base de données registry partagée, puis applique les migrations tenant à travers chaque tenant actif. Pour un contrôle plus fin, utilisez migrate-registry / migrate-tenants.

migrate <target>

Migre vers un point précis de l'historique, en avant ou en arrière. Nommez une migration pour y accéder ; la cible spéciale zero défait tout.

cargo run -- migrate 0003_add_slug      # forward to 0003
cargo run -- migrate 0001_initial       # roll back to 0001 (unapply 0002+)
cargo run -- migrate zero               # unapply EVERY migration

migrate --squash

Regroupe chaque migration en attente (non appliquée) en un seul diff nouvellement généré — l'échappatoire pour l'itération de développement, utile quand une pile de migrations à moitié terminées est plus simple à régénérer qu'à corriger. Elle refuse de toucher à tout ce qui est déjà appliqué.

cargo run -- migrate --squash

Le fichier régénéré enregistre les noms qu'il a regroupés dans sa liste replaces. Cela compte dès qu'une autre base de données entre en jeu : le checkout de votre collègue, le staging, ou la CI ont peut-être déjà appliqué certains des fichiers que vous venez de supprimer. Sans replaces, le CREATE TABLE du nouveau fichier entrerait en conflit là-bas ; avec, le runner réconcilie au lieu de cela (voir ci-dessous).

Réconciliation du squash

Un squash recrée l'état final des migrations qu'il remplace, donc ce que le runner doit faire dépend entièrement de ce que contient déjà la base de données cible. La décision est automatique :

état de la base de donnéesce qui se passe
fraîche — aucun historique, aucune tablele squash s'exécute réellement
chaque migration remplacée est dans le journalenregistrée, prédécesseurs mis en sommeil, aucun DDL
des tables existent mais le journal n'a aucun historiqueenregistrée, aucun DDL — le journal est aligné après coup sur les tables existantes
seulement certaines des lignes/tables remplacées sont présentesrefusé, en précisant ce qui manque

Le cas partiel est délibérément une erreur bloquante : aucun choix automatique n'y est sûr, donc le runner s'arrête et vous indique ce qu'il a trouvé plutôt que de deviner. Résolvez-le avec migrate --fake (ci-dessous).

Les migrations remplacées par un squash appliqué sont considérées comme appliquées, donc vous pouvez laisser les anciens fichiers sur le disque pour une ou deux versions — les déploiements qui ne les ont jamais exécutés migrent tout de même correctement vers l'avant.

Les migrations ordinaires (non-squash) ne sont pas affectées : une migration classique dont la table existe déjà échoue toujours bruyamment, car il s'agit d'un vrai conflit et non d'un historique équivalent connu.

migrate --fake <name>

Marque une migration comme appliquée sans exécuter son SQL — l'échappatoire opérateur pour quand la base de données est déjà dans l'état cible mais que le journal ne le sait pas (une base de données configurée hors bande, une table de journal supprimée, une migration partiellement réussie, un squash partiel refusé). Répétez le drapeau pour réparer plusieurs lignes à la fois.

cargo run -- migrate --fake 0004_add_indexes
cargo run -- migrate --fake 0004_add_indexes --system        # framework's own chain
cargo run -- migrate --fake 0004_add_indexes --all-tenants   # every active tenant

Le nom est d'abord validé par rapport au répertoire de migrations, donc une faute de frappe ne peut pas créer une fausse ligne. Le marquage est idempotent.

--system cible la propre chaîne de migrations du framework (system/migrations/, enregistrée dans __rustango_system_migrations__) plutôt que celle de votre projet. --all-tenants diffuse le marquage à travers chaque tenant actif, en rapportant chacun et en continuant malgré les échecs — les tables du framework vivent par tenant, donc les réparer est une tâche par tenant.

downgrade [N]

Annule les N dernières migrations appliquées (par défaut 1) — le migrate:rollback de Laravel. Chaque migration doit être réversible : les changements de schéma s'annulent automatiquement, mais les opérations de données nécessitent un reverse_sql défini, sinon l'annulation échoue.

cargo run -- downgrade                  # one step
cargo run -- downgrade 3                # three steps

showmigrations / status

Liste chaque migration et indique si elle a été appliquée. [X] signifie appliquée, [ ] signifie encore en attente.

cargo run -- showmigrations
cargo run -- status                     # alias

Sortie :

[X] 0001_initial
[X] 0002_add_status
[ ] 0003_add_slug

Migrations de données

add-data-op

Ajoute une étape de données en SQL brut à une migration sans modifier de JSON à la main. Utilisez-la quand vous devez transformer des lignes existantes — remplir rétroactivement une colonne, nettoyer des données — dans le cadre d'une migration. Le résultat est une migration de données qui exécute du SQL brut, générée pour vous depuis la ligne de commande.

# New migration with up + down
cargo run -- add-data-op \
    --sql "UPDATE posts SET slug = lower(title)" \
    --reverse-sql "UPDATE posts SET slug = NULL" \
    --name backfill_post_slugs

# Append to an existing migration
cargo run -- add-data-op \
    --to 0003_add_slug \
    --sql "UPDATE posts SET slug = id::text"

# Irreversible (no rollback)
cargo run -- add-data-op \
    --sql "DELETE FROM legacy_data" \
    --name purge_legacy
FlagRequisDescription
--sql <SQL>ouiSQL avant exécuté lors de migrate
--reverse-sql <SQL>nonSQL de retour en arrière lors de unapply ; omettez-le pour une opération irréversible
--name <name>nonSuffixe de nom pour la nouvelle migration ; par défaut data_op
--to <migration>nonAjoute à une migration existante au lieu d'en créer une

Omettez --reverse-sql et l'étape est marquée reversible: false — toute tentative de l'annuler échoue immédiatement.


Générateurs de projet / d'app

cargo rustango new <name> (binaire séparé)

Crée un nouveau projet Rustango — comme laravel new ou rails new. C'est un outil séparé, donc installez-le d'abord avec cargo install cargo-rustango. Choisissez parmi trois modèles :

cargo rustango new myblog                          # default = fullstack (ORM + admin)
cargo rustango new myapi --template api            # JSON-only, no admin
cargo rustango new shop --template tenant          # multi-tenancy

Écrit :

<name>/
  Cargo.toml
  .env.example
  .gitignore
  rust-toolchain.toml
  docker-compose.yml
  Dockerfile                                (deploy image — multi-stage, release, non-root)
  Dockerfile.dev                            (cargo-watch image docker-compose.yml builds)
  .dockerignore
  README.md
  config/default.toml                       (shared knobs)
  config/{dev,staging,prod}_settings.toml   (per-tier overrides)
  migrations/                               (your app's migrations)
  system/migrations/                        (tenant template — framework tables, generated)
  src/{lib,main,models,views,urls}.rs

Le niveau config/ est la source des réglages : default.toml contient ce que tous les environnements partagent et un <env>_settings.toml le surcharge, sélectionné à l'exécution par RUSTANGO_ENV (dev par défaut) — un cargo run tout neuf fonctionne donc sans aucune retouche TOML. Ensemble, ils portent le réglage du pool [database], [admin], [mcp] et la politique secure_cookies — et ce sont eux que lit l'audit de réglages de check --deploy. Le contenu par niveau est détaillé dans Scaffolding.

Le modèle tenant fournit un dossier system/migrations/ vide. Les propres tables du framework (rustango_orgs, rustango_users, rôles/permissions, …) sont générées dans ce dossier à partir des modèles compilés lors du premier cargo run -- migrate — il n'y a pas de JSON d'amorçage livré à la main. Voir migrate / migrate-registry.

startapp <name> [flags]

Crée une nouvelle app (un module de fonctionnalité) sous src/<name>/. Utilisez-la pour regrouper les modèles, vues et URLs d'une partie de votre projet.

cargo run -- startapp blog
cargo run -- startapp shop --with-manage-bin             # also writes src/bin/manage.rs
cargo run -- startapp shop --into apps                   # write under src/apps/shop/ instead

Crée :

src/<name>/
  mod.rs
  models.rs
  views.rs
  urls.rs

Sûr à réexécuter — les fichiers existants sont laissés intacts. Une étape manuelle : ajouter pub mod <name>; à src/lib.rs pour que Rust compile le nouveau module.


Générateurs de fichiers (make:*)

Ceux-ci créent des fichiers de démarrage pour des briques courantes — à l'image des commandes make:* de Laravel (make:controller, make:model, …). Chaque générateur écrit dans src/<snake_name>.rs (ou tests/<snake_name>.rs pour make:test) et :

  • Vérifie que le nom est valide (PascalCase, lettres/chiffres/underscore).
  • Le convertit en snake_case pour le nom de fichier (PostViewSet → post_view_set.rs).
  • Ne remplace pas un fichier existant.
  • Vous rappelle d'ajouter pub mod X; à votre lib.rs.

make:viewset <Name> [--model <Model>] [--tenant | --no-tenant] [--crate <path>]

Génère une ressource REST CRUD complète pour un modèle.

Deux modèles, choisis pour vous. Le #[derive(ViewSet)] à pool unique capture un seul pool au moment du montage, ce qui est faux pour un projet tenancy — le générateur choisit donc entre deux formes. Ordre de résolution : --no-tenant l'emporte, puis --tenant, puis la détection automatique — tenancy dans la liste de features de la dépendance rustango du Cargo.toml — et sinon le modèle à pool. Quand la détection automatique retient le mode tenant, elle l'annonce sur stdout et nomme --no-tenant comme override.

cargo run -- make:viewset PostViewSet --model Post
cargo run -- make:viewset PostViewSet --model Post --no-tenant   # forcer la forme à pool

Modèle à pool — src/post_view_set.rs généré :

#[derive(ViewSet)]
#[viewset(model = Post, fields = "id, ", filter_fields = "", search_fields = "", page_size = 20)]
pub struct PostViewSet;

Montez-le avec : .merge(PostViewSet::router("/api/posts", pool.clone())).

Modèle tenant — pas un derive. Il émet un pub fn router() -> Router<()> construit à partir de ViewSet::for_model(Post::SCHEMA).tenant_router("/api/posts"), avec toute la chaîne du builder (fields / filter_fields / search_fields / ordering / page_size / permissions_for_model / read_only) ébauchée en lignes commentées. La connexion est résolue par requête via l'extracteur Tenant au lieu d'être capturée une fois au montage, donc un seul router() sert tous les tenants.

Montez-le avec : .merge(crate::viewsets::post::router()) — et non avec la ligne PostViewSet::router(path, pool) ci-dessus.

--crate <path> renomme la crate du framework dans les lignes use générées, pour les projets qui importent rustango sous un autre nom.

make:serializer <Name> [--model <Model>]

Génère une structure #[derive(Serializer)] — contrôle la façon dont un modèle est converti vers et depuis JSON.

cargo run -- make:serializer PostSerializer --model Post

make:form <Name>

Génère une structure #[derive(Form)] pour valider et traiter une saisie de formulaire.

cargo run -- make:form ContactForm

make:job <Name>

Génère un jobs::Job — une struct de payload plus l'implémentation du trait, avec NAME, MAX_ATTEMPTS et async fn run(&self). C'est du travail que vous mettez en file depuis un handler et qu'un worker exécute ensuite.

run ne reçoit que la payload : pas de pool, pas de tenant, pas de contexte de requête. Portez dans ses champs tout ce dont la tâche a besoin.

cargo run -- make:job EmailDigestJob

make:scheduled <Name>

Génère une tâche à intervalle fixe pour scheduler::Scheduler — la forme que make:job émettait avant de générer un véritable job.

cargo run -- make:scheduled NightlySweep

make:worker <Name>

Génère un binaire de worker autonome pour src/bin/ — un processus qui vide la file de tâches et ne sert aucun HTTP. Lancez-le à côté du processus web, ou comme son propre conteneur.

La forme est courte et facile à écrire de travers d'une manière qui ne se voit qu'en production : un worker qui attend tokio::signal::ctrl_c() traite SIGINT mais pas SIGTERM, qui est précisément ce qu'envoient docker stop, Kubernetes et systemd. La vidange ne s'exécute alors jamais, le conteneur est tué à l'expiration de son délai de grâce, et les tâches en vol sont perdues sans que rien ne soit journalisé. Le worker généré attend shutdown::shutdown_signal(), qui traite les deux signaux.

cargo run -- make:worker JobsWorker

make:notification <Name>

Génère une structure de notification qui construit un email — comme le make:notification de Laravel.

cargo run -- make:notification WelcomeEmail

make:middleware <Name>

Génère une fonction middleware — du code qui s'exécute avant et après chaque requête (vérifications d'authentification, journalisation, etc.). « axum » est le framework web sur lequel Rustango est construit, donc l'ébauche correspond à la forme du middleware d'axum.

cargo run -- make:middleware AuditLog

make:test <Name>

Génère un test d'intégration dans tests/ qui utilise TestClient pour effectuer des requêtes contre votre app.

cargo run -- make:test post_smoke

Utilitaires de base de données

db:info

Affiche à quelle base de données cette build est configurée pour parler, sans se connecter. Elle imprime la version du framework, les pilotes de base de données compilés (fonctionnalités Cargo postgres/mysql), l'URL de connexion avec le mot de passe masqué, et le backend détecté. Comme elle n'ouvre jamais de connexion, elle est pratique en CI ou dans des conteneurs où la base de données n'est pas encore prête mais où vous voulez confirmer que les réglages sont corrects.

cargo run -- db:info

db:dump [--out <path>] [--data-only|--schema-only] [--no-owner]

Sauvegarde votre base de données en exécutant pg_dump contre DATABASE_URL — comme php artisan db:dump. Par défaut, le SQL part sur stdout (pour pouvoir le rediriger dans un pipe) ; passez --out <path> (-o) pour écrire un fichier à la place. --data-only et --schema-only correspondent directement aux drapeaux de pg_dump, et --no-owner supprime les lignes OWNER. Vous devez avoir pg_dump installé et dans votre PATH.

cargo run -- db:dump > backups/before-migrate.sql    # stdout → file
cargo run -- db:dump --out backups/before-migrate.sql

La ligne d'état running: pg_dump … part sur stderr : elle reste donc hors de la redirection et hors d'un tube. Jusqu'à #1404 elle partait sur stdout et se retrouvait en première ligne du fichier .sql.

db:restore <path> [--clean]

Recharge un fichier de sauvegarde dans votre base de données — l'inverse de db:dump. Elle exécute le fichier via psql contre DATABASE_URL avec ON_ERROR_STOP=1, donc elle s'arrête à la première erreur. Ajoutez --clean pour effacer le schéma existant au préalable (elle préfixe DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;) afin que la restauration se fasse sur une base de données vide. Vous devez avoir psql dans votre PATH.

cargo run -- db:restore backups/before-migrate.sql
cargo run -- db:restore backups/before-migrate.sql --clean

Commandes système

version / --version

Affiche la version du framework Rustango.

$ cargo run -- version
rustango 0.57.11

about

Affiche un instantané de votre environnement : version du framework, modèles et apps enregistrés, si la base de données est accessible, et les variables d'environnement clés. Utile à joindre aux tickets de support en cas de problème.

$ cargo run -- about
rustango
  version:        0.57.11
  models:         3 registered
  apps:           1 (blog)
  RUSTANGO_ENV:   local
  DATABASE_URL:   postgres://***@localhost:5433/myblog
  db_connect:     ok

check [--deploy]

Exécute des vérifications de santé sur votre projet. Ajoutez --deploy pour les vérifications plus strictes de préparation à la production.

Vérifications toujours actives :

  • ≥ 1 modèle enregistré via inventory
  • Base de données accessible (SELECT 1)
  • Des modèles enregistrés mais aucune migration sur le disque (il ne compare pas les nombres — un répertoire migrations/ existant est signalé en info, quel que soit son contenu)

Avec --deploy :

  • RUSTANGO_ENV vaut prod ou production
  • RUSTANGO_SESSION_SECRET défini et ≥ 32 octets (la clé HMAC pour les cookies + JWT ; SECRET_KEY n'est jamais lu par le framework)
  • DATABASE_URL défini
  • RUSTANGO_APEX_DOMAIN défini — l'avertissement se déclenche pour tout projet lorsqu'il est absent ou localhost, et le dit ; les projets mono-tenant peuvent l'ignorer
  • DATABASE_URL pointant vers localhost / 127.0.0.1 (avertissement — en production, généralement un hôte managé)
  • RUSTANGO_BIND commençant par 127.0.0.1 (avertissement — en loopback seul, aucun trafic externe n'est accepté)
  • Un audit du palier de configuration sur votre TOML, signalant les valeurs de dev laissées dans un palier prod (nécessite la fonctionnalité config)
  • Meta.required_db_vendor / required_db_features de chaque modèle enregistré, vérifiés contre le dialecte réellement connecté
$ cargo run -- check --deploy
running rustango system check (deploy mode)...
  [info]    3 models registered via inventory
  [info]    database reachable
  [info]    4 migration(s) on disk
  [info]    RUSTANGO_SESSION_SECRET length OK
all checks passed

Se termine avec un code non nul si une vérification de niveau erreur échoue. Les avertissements seuls ne provoquent pas d'échec.

docs

Ouvre la documentation de Rustango (https://docs.rs/rustango) dans votre navigateur. Elle affiche toujours aussi l'URL, afin de rester utile sur un serveur sans interface graphique.

cargo run -- docs

--help / help

Liste chaque commande avec une description d'une ligne. En mode tenancy, les commandes multi-tenant listées ci-dessous sont ajoutées également.


Commandes de tenancy

Ces commandes n'existent que dans les projets multi-tenant (une application servant de nombreux clients/organisations isolés). Elles n'apparaissent que lorsque le projet est compilé avec features = ["tenancy"] ET que Cli::new() est chaîné avec .tenancy().

Tout ce que la console opérateur sait faire, ces commandes le savent aussi — pour qu'une action puisse tourner dans un hook de déploiement, dans un cron, ou sur une machine où personne ne peut ouvrir de navigateur.

menu / actions

Il y a plus de quarante verbes de tenancy. --help vous dit qu'ils existent ; le menu vous aide à en exécuter un que vous n'avez jamais lancé.

cargo run -- menu
  rustango manage — pick an action

  TENANTS
     1) list-tenants             every tenant in the registry
     2) create-tenant            provision a new tenant
     3) edit-tenant              change routing and display config
     …
  HOSTNAMES
     7) list-hosts               every hostname a tenant answers on
     …
     q  quit
     ?  every other verb: cargo run -- --help

Choisissez un numéro (ou tapez le verbe) : il ne demande que ce que le verbe ne demandera pas lui-même, affiche la ligne de commande qu'il s'apprête à exécuter, l'exécute, puis revient pour l'action suivante. Il repasse par le même dispatcher que les flags, il ne peut donc pas s'en écarter.

Voisin mais différent : wizard est une mise en place unique ; menu est la liste permanente de ce que vous pouvez faire ensuite.

wizard / init

Mène un projet neuf de « je viens de l'échafauder » à un tenant fonctionnel, un opérateur et un superutilisateur de tenant. Chaque étape est optionnelle — tapez n pour en sauter une.

cargo run -- wizard

init-tenancy

Ne fait rien — conservée pour compatibilité. Le framework ne fournit plus de migrations d'amorçage écrites à la main. Ses propres tables (rustango_orgs, rustango_operators, rustango_users, rôles/permissions, …) sont générées dans system/migrations/ à partir des modèles compilés — le flux habituel (modèles → makemigrations → migrate) — et appliquées par migrate / migrate-registry, qui les génèrent à la demande si les fichiers sont absents.

cargo run -- init-tenancy   # does nothing now; kept so old scripts don't break

Les anciennes versions écrivaient ici 0001_rustango_*_initial.json ; ce flux figé a disparu. Pour provisionner, exécutez simplement cargo run -- migrate. Un modèle utilisateur personnalisé (.user_model::<AppUser>()) passe par le même system/migrations/ généré — voir Modèle utilisateur personnalisé.

migrate-registry

Applique uniquement les migrations registry — les tables partagées, inter-tenant. Le registry contient rustango_orgs et rustango_operators plus toute table de scope registry que vous définissez. Les tables tenant ne sont pas touchées.

cargo run -- migrate-registry

migrate-tenants

Applique les migrations tenant à chaque tenant actif, l'un après l'autre. Chaque tenant utilise sa propre connexion (son propre schéma ou sa propre base de données), et si un tenant échoue, les autres continuent tout de même — la commande rapporte le résultat par tenant à la fin.

cargo run -- migrate-tenants

Pour le cas courant, un simple migrate fait déjà registry en premier, puis tenants — n'utilisez migrate-tenants que lorsque vous avez besoin de cette étape seule.

runserver / run-server

Démarre le serveur web multi-tenant. Dans un projet de tenancy, c'est équivalent au simple cargo run ; la forme nommée existe pour que des binaires personnalisés qui analysent leurs propres arguments puissent tout de même la déclencher.

cargo run                        # implicit
cargo run -- runserver           # explicit

create-tenant <slug> [options]

Met en place un nouveau tenant (client/organisation) et applique les migrations tenant à celui-ci. Le <slug> est son identifiant court. Pas sûr à réexécuter : l'appeler à nouveau sur un slug existant est refusé d'emblée avec tenant slug `<slug>` already exists (tenancy/provision.rs:599), avant toute autre opération. Ce qui ne duplique rien.

cargo run -- create-tenant acme --display-name "ACME Corp"
cargo run -- create-tenant beta --mode database --database-url postgres://...
FlagDescription
--display-name <name>Libellé lisible affiché dans les barres latérales d'administration
--mode schema | databaseMode de stockage (par défaut : schema)
--database-url <url>URL de base de données propre au tenant (requise en mode database)
--host-pattern <pattern>Remplace le motif d'hôte utilisé par SubdomainResolver
--no-migrateIgnore l'application des migrations à scope tenant après le provisionnement
--backend postgres | mysql | sqlitePilote pour un tenant en mode base (défaut : postgres). Validé par rapport à --mode
--schema-name <s>Remplace le nom de schéma généré en mode schéma
--port <n>Port sur lequel le tenant est joignable, pour le routage
--path-prefix <s>Préfixe de chemin sous lequel le tenant est joignable, pour le routage

edit-tenant <slug> [options]

Modifie la configuration de routage et d'affichage d'un tenant — les mêmes champs que la page d'édition de la console opérateur.

cargo run -- edit-tenant acme --host-pattern shop.example.com
cargo run -- edit-tenant acme --display-name "ACME Inc" --deactivate
cargo run -- edit-tenant acme --clear host-pattern
FlagDescription
--display-name <name>Libellé lisible par un humain
--host-pattern <host>Nom d'hôte nu auquel le tenant répond
--path-prefix <path>Un segment avec slash initial, p. ex. /acme
--port <n>Port sur lequel le tenant est apparié
--database-url <url>Fait tourner l'URL de connexion du tenant
--activate / --deactivateMet le tenant en service ou hors service
--clear <field>Vide host-pattern, path-prefix ou port

Seuls les champs que vous nommez sont touchés. « Laisser tel quel » et « vider » sont deux instructions différentes — c'est à cela que sert --clear, qui évite de dépendre de --host-pattern "", que certains shells et runners CI avalent.

Les valeurs sont validées comme create-tenant les valide : un motif d'hôte portant un port, ou un préfixe de chemin que le résolveur ne pourrait jamais produire, est refusé plutôt que stocké pour n'apparier silencieusement jamais rien.

Faire tourner --database-url évince le pool en cache du tenant, de sorte que la requête suivante se reconnecte avec le nouvel identifiant ; les autres modifications laissent les connexions chaudes tranquilles.

test-tenant-connection <url> [flags]

Sonde une URL de base de données avant que vous ne vous y engagiez — elle se connecte et, par défaut, écrit puis annule, si bien qu'un identifiant en lecture seule est détecté ici plutôt qu'à la première requête du tenant.

cargo run -- test-tenant-connection postgres://user:pw@host/db
cargo run -- test-tenant-connection "$URL" --no-write-probe --timeout 5

drop-tenant <slug> [--confirm <slug>]

Désactive un tenant en réglant active = false. C'est l'option souple et réversible — les données du tenant restent sur le disque, et réactivez-le avec edit-tenant <slug> --activate. Réexécuter create-tenant ne marche pas : la ligne Org existe toujours, donc le slug est refusé comme doublon. Quand vous n'êtes pas en mode interactif (aucun terminal attaché), vous devez passer --confirm <slug> avec le slug retapé pour confirmer.

cargo run -- drop-tenant acme --confirm acme

purge-tenant <slug> [--confirm <slug>] [--purge-database]

Supprime définitivement un tenant. Elle supprime le schéma du tenant et retire sa ligne de rustango_orgs, sans possibilité d'annulation. Quand vous n'êtes pas en mode interactif (aucun terminal attaché), vous devez passer --confirm <slug> avec le slug retapé. Pour les tenants en mode database, la base de données sous-jacente est laissée en place sauf si vous passez aussi --purge-database. Sans ce drapeau, la commande refuse purement et simplement pour les tenants en mode base — elle ne supprime pas la ligne Org en laissant la base, elle ne fait rien du tout (tenancy/manage/tenants.rs:479).

cargo run -- purge-tenant acme --confirm acme
cargo run -- purge-tenant beta --confirm beta --purge-database   # database-mode: also DROP DATABASE

list-tenants

Liste chaque tenant avec son mode de stockage et son statut actif/inactif.

cargo run -- list-tenants

Noms d'hôte

Un tenant est joignable à son sous-domaine et, en option, aux noms d'hôte supplémentaires que vous lui attachez. Un nom d'hôte mène à exactement un tenant.

cargo run -- list-hosts acme
cargo run -- add-host acme shop.example.com
cargo run -- set-host-enabled acme shop.example.com --off   # le mettre en attente
cargo run -- remove-host acme shop.example.com
VerbeCe qu'il fait
list-hosts <slug>Tous les noms d'hôte du tenant, l'hôte de base en premier
add-host <slug> <hostname>En attache un. Refusé si un autre tenant le revendique déjà
remove-host <slug> <hostname>Le détache
set-host-enabled <slug> <hostname> --on|--offServir ou mettre en attente

La mise en attente (--off) conserve la ligne tout en retirant l'hôte du service — utile pendant la propagation DNS, ou pour retirer un domaine que vous voudrez peut-être récupérer.

Les noms d'hôte sont normalisés à l'entrée (minuscules, sans schéma, sans port, sans chemin), car la valeur stockée est comparée octet par octet à l'en-tête Host. Les verbes affichent ce qui a été stocké, pas ce que vous avez tapé.

L'hôte de base provient du host_pattern du tenant et n'a pas de ligne propre : il ne peut donc pas être retiré ici — changez-le avec edit-tenant --host-pattern. Il n'y a délibérément pas de rename-host : retirez puis rajoutez, pour que le changement atteigne aussi les autres pods en cours d'exécution.

create-operator <username> --password <pwd>

Crée un opérateur — un administrateur global qui peut gérer chaque tenant depuis une console inter-tenant. Les opérateurs vivent dans le registry partagé, pas dans un tenant en particulier.

cargo run -- create-operator admin --password letmein
cargo run -- create-operator admin --generate     # en afficher un aléatoire à la place

Omettez --password sur un terminal et il le demande sans écho.

list-operators

Chaque opérateur, avec son statut actif et sa date de création, plus un décompte de ceux encore actifs.

cargo run -- list-operators

set-operator-active <username> --on|--off

Coupe ou rétablit l'accès d'un opérateur. La ligne reste dans les deux cas, de sorte que « qui a fait ça ? » se résout encore plus tard — et la console la relit à chaque requête, si bien qu'une désactivation prend effet au clic suivant plutôt qu'à l'expiration du cookie.

cargo run -- set-operator-active grace --off   # départ d'un collègue
cargo run -- set-operator-active grace --on

Deux choses qu'il refuse, pour la même raison que la console :

  • Désactiver le dernier opérateur actif. Cela enferme tout le monde dehors, et seul un shell sur le registry pourrait le défaire.
  • Se désactiver soi-même, quand la requête vient de la console. La CLI n'a pas de session dont s'exclure, seule la première règle s'y applique.

La direction est obligatoire — deviner reviendrait soit à retirer un accès, soit à en accorder un, et les deux sont fautifs s'ils se font en silence. Régler l'état déjà en place est signalé et réussit, pour qu'un script de provisioning relancé ne passe pas au rouge.

create-user <tenant> <username> --password <pwd> [--superuser]

Crée un utilisateur au sein d'un tenant — avec --superuser, un administrateur, mais toujours limité à un seul tenant.

cargo run -- create-user acme alice --password hunter2 --superuser

--superuser règle is_superuser = true pour cet utilisateur au sein du tenant. Cela en fait un administrateur du tenant (accès en écriture complet dans l'admin du tenant), mais cela ne lui donne jamais accès à la console opérateur inter-tenant.

create-role <tenant> <name>

Crée un rôle (un ensemble nommé de permissions) au sein d'un tenant.

cargo run -- create-role acme editor

list-roles <tenant>

Liste les rôles définis dans un tenant donné.

cargo run -- list-roles acme

assign-role <tenant> <username> <role>

Donne à un utilisateur l'un des rôles du tenant.

cargo run -- assign-role acme alice editor

revoke-role <tenant> <username> <role>

Retire un rôle à un utilisateur — l'inverse d'assign-role.

cargo run -- revoke-role acme alice editor

grant-perm <tenant> <role-name|username> <codename> [--role]

Accorde une seule permission. Par défaut, le deuxième argument est un nom d'utilisateur, donc la permission va directement à cet utilisateur ; ajoutez --role pour l'accorder à un rôle à la place. Les noms de code de permission suivent le format <app>.<action>_<model> (blog.add_post, blog.change_post, …). La fonctionnalité auto_create_permissions crée automatiquement les quatre noms de code CRUD standards pour tout modèle marqué #[rustango(permissions)].

cargo run -- grant-perm acme alice blog.change_post           # grant to user alice
cargo run -- grant-perm acme editor blog.change_post --role   # grant to role editor

revoke-perm <tenant> <role-name|username> <codename> [--role]

Retire une permission — l'inverse de grant-perm. Cible un utilisateur par défaut ; ajoutez --role pour la retirer à un rôle à la place.

cargo run -- revoke-perm acme alice blog.change_post
cargo run -- revoke-perm acme editor blog.change_post --role

create-api-key <tenant> <username> [--label <s>]

Émet une clé API pour un utilisateur du tenant. Le jeton complet est affiché une seule fois et jamais plus — copiez-le maintenant, car seuls son préfixe et un hachage sont stockés.

cargo run -- create-api-key acme alice --label "ci-bot"

audit-cleanup

Élague les anciennes entrées du journal d'audit (rustango_audit_log) pour l'empêcher de grossir indéfiniment. Réduisez par âge (--days) ou par nombre (--keep-last), et limitez éventuellement à un seul tenant.

cargo run -- audit-cleanup --days 90                       # delete > 90 days old
cargo run -- audit-cleanup --keep-last 50                  # keep most recent 50 per row
cargo run -- audit-cleanup --keep-last 50 --tenant acme    # scoped
cargo run -- audit-cleanup --registry --days 90            # le journal du registry seul

Par défaut, il balaie le journal propre au registry et celui de chaque tenant actif. C'est dans le journal du registry que la console opérateur consigne ce qu'ont fait les opérateurs : il grossit donc avec l'usage de la console. Nommer un tenant (--tenant) demande ce tenant-là et laisse le registry tranquille. Un tenant cassé est signalé et compté plutôt que de mettre fin au balayage.

Voir ce qui s'est passé

Trois verbes en lecture seule qui affichent ce que la console rend — les réponses qu'il vous faut pendant un incident, sans navigateur ni client SQL.

cargo run -- list-runs                        # exécutions récentes de provisioning et de migration
cargo run -- list-runs --kind migrate --limit 50
cargo run -- show-run 42                      # les étapes d'une exécution
cargo run -- audit-log                        # qui a changé quoi, et quand
cargo run -- audit-log --pk acme --limit 100
VerbeFlags
list-runs--limit <n>, --kind provision|migrate, --state <s>
show-run <id>—
audit-log--limit <n>, --table <t>, --pk <v>, --operation <o>, --source <s>

list-runs affiche les plus récentes en premier, et ses filtres tournent dans la requête : chercher une exécution migrate en trouve donc une même si les plus récentes sont toutes des provisionings. show-run affiche l'en-tête de l'exécution et chaque étape enregistrée, ce qui vous dit où une panne s'est produite.

Les tenants créés depuis la CLI sont enregistrés aussi, marqués requested_by = cli, si bien que l'historique couvre les deux surfaces.

prewarm-pools

Ouvre à l'avance une connexion pour chaque tenant actif en mode database. Cela vaut le coup après un déploiement, un redémarrage du registry ou une rotation d'identifiants : cela transforme « la première requête vers chaque tenant paie la connexion » en une seule attente délibérée, et fait apparaître un tenant injoignable avant qu'un utilisateur ne le trouve.

cargo run -- prewarm-pools

La console opérateur a également un bouton pour cela, sur la liste des tenants.


Modèle utilisateur personnalisé (colonnes supplémentaires sur rustango_users)

Voici comment ajouter vos propres champs à la table utilisateur. Le User de tenant intégré possède sept colonnes fixes : id, username, password_hash, is_superuser, active, created_at, plus une colonne JSONB data (un blob JSON flexible) pour toute métadonnée supplémentaire par utilisateur. Pour la plupart des apps, cette colonne JSONB est tout ce dont vous avez besoin — pas de migration, pas de surcharge, pas de surprise.

Quand vous voulez des colonnes typées, indexables sur rustango_users à la place, il existe deux approches. Elles ne sont pas interchangeables ; choisissez celle qui correspond à l'étape de vie de votre projet.

Option 1 — Modèle de profil compagnon avec clé étrangère (fonctionne sur tout projet)

Idéale quand le projet existe déjà, ou quand vous préférez laisser la table User du framework comme source unique de vérité.

#[derive(rustango::Model)]
pub struct UserProfile {
    #[rustango(primary_key)] pub id: rustango::sql::Auto<i64>,
    #[rustango(fk = "rustango_users")] pub user_id: i64,
    #[rustango(max_length = 128, default = "''")] pub display_name: String,
    #[rustango(max_length = 64, default = "'UTC'")] pub timezone: String,
}

Exécutez cargo run -- makemigrations puis cargo run -- migrate, et vous avez une table d'extras typée liée à l'utilisateur par clé étrangère. Lisez-la avec l'ORM :

let profile = UserProfile::objects()
    .where_(UserProfile::user_id.eq(user.id.get().copied().unwrap()))
    .first(&pool).await?;            // Option<UserProfile>

Compromis : une ligne supplémentaire et une jointure à chaque accès. Avantage : zéro risque de casser l'authentification du framework.

Option 2 — Cli::user_model::<AppUser>() (uniquement pour un projet neuf)

N'utilisez ceci que sur un projet neuf où vous voulez les champs supplémentaires directement sur la table rustango_users elle-même. Comme AppUser est le modèle rustango_users, ses colonnes passent par le moteur ordinaire makemigrations → migrate : les tables du framework sont générées dans system/migrations/, donc les colonnes d'AppUser se retrouvent dans le CREATE TABLE rustango_users généré.

Étape 1. Définissez votre modèle. Il doit déclarer exactement chaque colonne requise par le framework (id, username, password_hash, is_superuser, active, created_at, data), plus vos extras. Chaque colonne supplémentaire doit soit autoriser NULL, soit avoir un default = "…".

use rustango::sql::Auto;

#[derive(rustango::Model, Debug, Clone)]
#[rustango(table = "rustango_users")]
pub struct AppUser {
    #[rustango(primary_key)] pub id: Auto<i64>,
    #[rustango(max_length = 64, unique)] pub username: String,
    #[rustango(max_length = 255)] pub password_hash: String,
    pub is_superuser: bool,
    pub active: bool,
    pub created_at: chrono::DateTime<chrono::Utc>,
    #[rustango(default = "'{}'")] pub data: serde_json::Value,
    // extras —
    #[rustango(max_length = 128, default = "''")] pub display_name: String,
    #[rustango(max_length = 64, default = "'UTC'")] pub timezone: String,
}
impl rustango::tenancy::TenantUserModel for AppUser {}

Étape 2. Branchez la surcharge dans main.rs :

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    rustango::manage::Cli::new()
        .api(my_app::urls::router())
        .tenancy()
        .user_model::<AppUser>()
        .run().await
}

Étape 3. Enregistrez AppUser au lieu du User du framework — un seul modèle peut revendiquer table = "rustango_users". Le générateur de squelette ne livre aucun JSON d'amorçage statique (seulement un system/migrations/ vide), donc il n'y a rien à supprimer ; il faut juste ne pas enregistrer aussi le User du framework.

Étape 4. Générez + appliquez :

cargo run -- makemigrations       # generates system/migrations/ with AppUser's columns
cargo run -- migrate              # creates rustango_users with your extras

Mises en garde :

  • Modifier AppUser plus tard est un changement de schéma ordinaire : réexécutez makemigrations pour émettre la migration AddColumn, puis migrate.
  • Un seul modèle peut correspondre à rustango_users. Enregistrer à la fois le User du framework et votre AppUser rend makemigrations ambigu — enregistrez AppUser seul. C'est la raison principale pour laquelle l'option 2 est réservée aux projets neufs ; sur un projet existant, l'option 1 évite le problème.
  • Le code d'authentification et d'administration du framework lit les sept colonnes essentielles par leur nom ; vos colonnes supplémentaires ne sont accessibles que via AppUser::objects().fetch(...).

Builder::user_model::<AppUser>() fait la même chose pour le code qui construit directement le Builder du serveur, sans passer par Cli.


Sous-commandes personnalisées

Vous pouvez ajouter vos propres commandes et les exécuter aux côtés des verbes intégrés. L'astuce consiste à inspecter vous-même les arguments et à traiter votre commande avant de passer le reste à Cli::run. Deux façons de le faire :

En ligne dans src/main.rs (pas de binaire supplémentaire) :

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    let args: Vec<String> = std::env::args().skip(1).collect();
    if matches!(args.first().map(String::as_str), Some("import-csv")) {
        let url = std::env::var("DATABASE_URL")?;
        let pool = rustango::sql::Pool::connect_postgres(&url).await?;
        return my_csv_importer::run(&pool, &args[1..]).await;
    }
    rustango::manage::Cli::new().api(urls::api()).run().await
}

Via --with-manage-bin (src/bin/manage.rs séparé) :

cargo run -- startapp app --with-manage-bin

Puis dans src/bin/manage.rs :

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    let args: Vec<String> = std::env::args().skip(1).collect();
    let url = std::env::var("DATABASE_URL")?;
    let pool = rustango::sql::Pool::connect_postgres(&url).await?;

    match args.first().map(String::as_str) {
        Some("import-csv") => my_csv_importer::run(&pool, &args[1..]).await,
        _ => rustango::migrate::manage::run(&pool, "./migrations".as_ref(), args)
            .await
            .map_err(Into::into),
    }
}

Exécutez vos propres commandes exactement comme les commandes intégrées : cargo run -- import-csv path/to/file.csv (ou cargo run --bin manage -- import-csv … en utilisant --with-manage-bin).


Flux de travail courants

Mise en place d'un projet pour la première fois (mono-tenant)

cargo rustango new myapp
cd myapp
cp .env.example .env             # edit DATABASE_URL
docker compose up -d
cargo run -- migrate
cargo run                        # serve at :8080

Mise en place d'un projet pour la première fois (tenancy)

cargo rustango new myapp --template tenant
cd myapp
cp .env.example .env             # edit DATABASE_URL + RUSTANGO_APEX_DOMAIN
docker compose up -d
cargo run -- migrate                                      # registry + tenants
cargo run -- create-operator admin --password letmein
cargo run -- create-tenant acme --display-name "ACME Inc" \
                  --host-pattern acme.localhost
cargo run -- create-user acme alice --password tenantpw --superuser
cargo run                        # serve at :8080

Ajout de tenants une fois l'app déjà en fonctionnement

Une véritable application de tenancy accumule généralement des modèles et des migrations bien avant l'arrivée de son premier tenant. Ce flux fonctionne à n'importe quel moment de la vie du projet :

# 1. (any time) develop user models — define structs with #[derive(Model)],
#    add `pub mod ...;` to src/lib.rs.
# 2. Generate scope-aware migrations. In a tenancy project this writes
#    up to TWO files: one tagged registry-scope (touches Org/Operator),
#    one tagged tenant-scope (touches User + your models). Pre-v0.24.2
#    this used to dump everything into one tenant-scoped file and
#    crash on `create-tenant` — see the changelog.
cargo run -- makemigrations

# 3. Apply migrations. `migrate` is scope-aware: it runs registry-
#    scoped files once against the registry pool first, then fans
#    tenant-scoped files across every active tenant.
cargo run -- migrate

# 4. Provision a NEW tenant whenever (could be days, weeks, many
#    migrations later). The tenancy code applies every accumulated
#    tenant-scoped migration to the new tenant's schema in one pass —
#    the new tenant arrives at the same schema state as existing ones.
cargo run -- create-tenant acme --display-name "ACME Inc" \
                  --host-pattern acme.localhost
cargo run -- create-user acme alice --password tenantpw --superuser

Pourquoi c'est sûr :

  • #[rustango(scope = "registry")] sur Org/Operator maintient les changements aux tables partagées hors des migrations par tenant.
  • migrate-tenants visite chaque tenant actif et applique uniquement les migrations tenant — les fichiers registry sont ignorés.
  • create-tenant exécute ce même passage migrate-tenants contre le schéma du nouveau tenant, qui démarre donc entièrement à jour sans correctif manuel.

Ajouter un modèle

cargo run -- startapp blog        # if not done yet
# Edit src/blog/models.rs — add #[derive(Model)]
# Add `pub mod blog;` to src/lib.rs
cargo run -- makemigrations
cargo run -- migrate

Ajouter une API JSON pour ce modèle

cargo run -- make:viewset PostViewSet --model Post
# Edit src/post_view_set.rs — fill in field lists
# Mount in src/urls.rs
cargo run                        # GET /api/posts now works

Ajouter un remplissage rétroactif de données

cargo run -- add-data-op \
    --sql "UPDATE posts SET slug = lower(title) WHERE slug IS NULL" \
    --reverse-sql "UPDATE posts SET slug = NULL" \
    --name backfill_post_slugs
cargo run -- migrate

Audit avant déploiement

cargo run --release -- check --deploy

Annuler la dernière migration

cargo run -- downgrade 1

Appliquer une migration de tenancy à un scope spécifique

cargo run -- migrate-registry            # registry-scoped only
cargo run -- migrate-tenants             # tenant-scoped, fan-out across orgs

Décommissionner un tenant

cargo run -- drop-tenant acme            # soft (reversible)
cargo run -- purge-tenant acme           # hard (drops schema/db)

Réglage du pool par tenant (v0.27.7+)

Les tenants en mode database obtiennent leur propre pool de connexions (un PgPool — un ensemble de connexions de base de données réutilisées), mis en cache par slug dans TenantPools. Par défaut, un pool est construit de façon paresseuse, à la première requête du tenant, sauf si vous activez le préchauffage. Les réglages se trouvent sur TenantPoolsConfig :

ChampDéfautObjet
max_cached_database_pools64Plafond du cache de pools. Une fois plein, le prochain tenant non mis en cache échoue (pas d'éviction silencieuse).
database_pool_max_connections16max_connections par pool. À garder petit pour qu'un éparpillement de tenants n'épuise pas le max_connections de PG.
database_pool_min_connections0Garde N connexions chaudes en permanence. ≥1 réduit la latence de la première requête en payant l'aller-retour TCP/TLS/auth au démarrage.
database_pool_acquire_timeout30sDurée d'attente de pool.acquire() avant l'erreur PoolTimedOut.
database_pool_idle_timeout10 minFerme les connexions inactives après cette durée. Protège contre les coupures dues à l'équilibreur de charge / à idle_in_transaction_session_timeout.
database_pool_max_lifetime30 minForce la rotation des connexions pour que les identifiants louvés via vault soient renouvelés.
prewarm_active_tenantsfalseSi vrai, Server::Builder::serve appelle prewarm_database_tenants() au démarrage.

Préchauffage au démarrage

Deux façons de le déclencher :

  1. Automatique — réglez prewarm_active_tenants = true sur le TenantPoolsConfig que vous passez à TenantPools::new(...).config(...). Server::Builder::serve exécute le préchauffage avant de se lier au port.

  2. Verbe CLI — cargo run -- prewarm-pools construit les pools pour chaque tenant actif en mode database et se termine. Utile comme hook post-déploiement (par exemple après une rotation d'identifiants), ou pour vérifier que chaque tenant est accessible avant de basculer un équilibreur de charge.

Le préchauffage parcourt Org::objects().where(active = true, storage_mode = "database") et s'arrête court quand le plafond du cache est atteint (rapporté comme skipped_cap dans le [PrewarmReport]). Les échecs de construction par tenant journalisent un tracing::warn! mais n'interrompent pas la boucle.

Traçage

tenant_pool_init est un tracing::info_span! qui enveloppe la construction du pool sur le chemin froid, et les événements qu'il contient portent le target rustango::tenancy::pools. Abonnez-vous-y pour voir la latence de construction par tenant :

INFO rustango::tenancy::pools: tenant pool connected (database mode)
     slug=acme elapsed_ms=42 min_conn=1 max_conn=4

Activez-le avec RUST_LOG=rustango::tenancy::pools=info. Un filtre sur crate::tenancy::pools ne correspond à rien — un target est une chaîne, pas un chemin ; voir Journalisation.

Piège de configuration — TLD .local sur macOS

Si vous accédez à l'admin du tenant via http://acme.local:8080/admin/ sur macOS et constatez une pause de 5 secondes à chaque requête : c'est Bonjour / mDNS, pas Rustango. Le résolveur de macOS traite .local de façon spéciale et attend le délai complet de mDNS avant de retomber sur /etc/hosts. Deux corrections :

  1. Utiliser un TLD différent : 127.0.0.1 acme.localhost fonctionne sans délai. localhost est réservé (RFC 6761) et évite mDNS.
  2. Exécuter dnsmasq avec une zone .local pointant vers 127.0.0.1 pour que l'OS obtienne une réponse immédiate.

Confirmez avec curl -w "%{time_connect}\n" : si time_connect affiche environ 5s mais tombe à quelques millisecondes avec --resolve acme.local:8080:127.0.0.1, vous êtes bien confronté à mDNS.


Voir aussi

Tous les verbes

Les sections ci-dessus détaillent les verbes courants. Ce tableau est la liste complète, tirée des deux dispatchers (migrate/manage.rs et tenancy/manage/mod.rs) plutôt que de la prose — un verbe absent du guide reste donc trouvable ici. Lancez <verb> --help pour ses drapeaux ; le texte d'aide fait foi, pas cette page.

Les verbes marqués T exigent la fonctionnalité tenancy et passent par Cli::tenancy().

Migrations et schéma

VerbeCe qu'il fait
makemigrations [name] / --empty <name>Génère une migration à partir du diff des modèles
migrate [target] / --dry-run / --squashApplique les migrations en attente
downgrade [N]Annule les N dernières migrations
showmigrations / statusListe les migrations et leur état d'application
sqlmigrate <name>Affiche le SQL qu'une migration exécuterait, sans l'exécuter
forget-pending <name>Supprime un JSON de migration non appliquée
add-data-op --sql <SQL> [--reverse-sql <SQL>]Ajoute une opération de données écrite à la main
inspectdb [--schema <s>] [--table <t>]Lit un schéma existant et produit du source #[derive(Model)]

Données

VerbeCe qu'il fait
dumpdataExporte des lignes en fixtures JSON
loaddata <fixture.json> [--fail-fast]Recharge des fixtures JSON
flush [--yes] [--app <label>] [--model <name>]Vide chaque table de modèle ; les drapeaux restreignent l'ensemble
prune [--model <name>] [--except <name>] [--pretend]Suppression en masse en flux ; --pretend signale sans supprimer
db:dump / db:restore / db:infoDump / restauration / inspection natifs
dbshellExécute le client natif (psql / mysql / sqlite3). N'a besoin que de DATABASE_URL, pas d'un pool fonctionnel — traité avant la construction du pool, il marche donc quand sqlx n'arrive pas à se connecter

Scaffolders et générateurs

VerbeCe qu'il fait
startapp <name>Génère un module d'application
make:viewset / make:serializer / make:formGénère un ViewSet, un Serializer ou un Form
make:job / make:scheduled / make:worker / make:middleware / make:notification / make:testGénère un job de file, une tâche à intervalle, un binaire de worker, un middleware, une notification ou un test
make:api_routes <app> [--tenant]Génère le module de routes API d'une application

Cache, sessions et courriel

VerbeCe qu'il fait
createcachetable / create-cache-table [--table <name>]Crée la table de cache (et la table de sessions quand les sessions vont en base)
clear-cache [--table <name>] / clearsessionsLa vide ; renvoie le nombre de lignes supprimées
sendtestemail --to <addr>Envoie un courriel de test fixe via le backend configuré

Introspection

VerbeCe qu'il fait
showmodels [--format plain|json] [--app <label>]Chaque modèle enregistré, trié pour une sortie déterministe
showurls [--format plain|json]Chaque route nommée, triée
check [--deploy]Contrôles de santé ; --deploy ajoute les audits de production
create-adminCrée une ligne AdminUser pour les projets utilisant admin::Builder::with_session_auth. Pas conditionné à tenancy — prend un simple &Pool et écrit dans rustango_admin_users, en créant la table si besoin. Le seul moyen d'obtenir un premier login admin sur un projet non-tenancy
about / version / --versionInformations de build et de version
docsOuvre la documentation

Utilisateurs et accès T

VerbeCe qu'il fait
create-superuser / set-superuserCrée un superutilisateur, ou promeut un utilisateur existant
create-user / create-operatorCrée un utilisateur de tenant ou un opérateur
reset-password / change-passwordRécupération du mot de passe d'un utilisateur de tenant
reset-operator-password / change-operator-passwordRécupération du mot de passe d'un opérateur
set-operator-activeActive ou désactive un opérateur
create-role / assign-role / revoke-role / list-rolesRôles
grant-perm / revoke-permPermissions par codename
seed-permissions [--slug <s>]Initialise les lignes de permission par défaut
create-api-keyÉmet une clé d'API

Tenants T

VerbeCe qu'il fait
create-tenant / edit-tenant / list-tenantsProvisionner, éditer, lister
drop-tenant / purge-tenantDésactiver (réversible) / détruire (non)
migrate-tenants / migrate-registryApplique les migrations à tous les tenants, ou au registre
migrate-tenant-storage <slug> --to schema|databaseDéplace un tenant d'un mode de stockage à l'autre
add-host / remove-host / list-hosts / set-host-enabledRoutage par hôte
test-tenant-connectionVérifie que la base d'un tenant est joignable
prewarm-poolsOuvre les pools de tenants avant la première requête
run-server / runserverLance le serveur multi-tenant
init / init-tenancy / wizard / menu / actionsPoints d'entrée de configuration et interactifs

Audit T

VerbeCe qu'il fait
audit-logLit la piste d'audit
audit-cleanupL'élague

MCP T

Documenté intégralement dans le guide MCP.

VerbeCe qu'il fait
create-agent / list-agents / rotate-agent-secretAgents
create-skill / list-skills / grant-skill / revoke-skillSkills
map-skill-permission / unmap-skill-permissionLie un skill à une permission
create-user-key / list-user-keys / revoke-user-keyIdentifiants MCP par utilisateur
list-runs / show-runHistorique des exécutions