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
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_blogest piloté parcargo run -- migrateet 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 unsrc/bin/manage.rs. Ceux-ci utilisentcargo 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
- Migrations de données
- Générateurs de projet / d'app
- Générateurs de fichiers (
make:*) - Utilitaires de base de données
- Commandes système
- Commandes de tenancy
- Sous-commandes personnalisées
- Flux de travail courants
- Tous les verbes
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/DropTableAddColumn/DropColumnAlterColumnType/AlterColumnNullable/AlterColumnDefault/AlterColumnMaxLengthAlterColumnUniqueCreateIndex/DropIndexAddCheckConstraint/DropCheckConstraintCreateM2MTable/DropM2MTable
NON détectés automatiquement (renommage vs suppression+ajout est ambigu) :
RenameTable,RenameColumn— utilisez--emptyet 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 neededet 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ées | ce qui se passe |
|---|---|
| fraîche — aucun historique, aucune table | le squash s'exécute réellement |
| chaque migration remplacée est dans le journal | enregistrée, prédécesseurs mis en sommeil, aucun DDL |
| des tables existent mais le journal n'a aucun historique | enregistrée, aucun DDL — le journal est aligné après coup sur les tables existantes |
| seulement certaines des lignes/tables remplacées sont présentes | refusé, 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
| Flag | Requis | Description |
|---|---|---|
--sql <SQL> | oui | SQL avant exécuté lors de migrate |
--reverse-sql <SQL> | non | SQL de retour en arrière lors de unapply ; omettez-le pour une opération irréversible |
--name <name> | non | Suffixe de nom pour la nouvelle migration ; par défaut data_op |
--to <migration> | non | Ajoute à 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;à votrelib.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_ENVvautprodouproductionRUSTANGO_SESSION_SECRETdéfini et ≥ 32 octets (la clé HMAC pour les cookies + JWT ;SECRET_KEYn'est jamais lu par le framework)DATABASE_URLdéfiniRUSTANGO_APEX_DOMAINdéfini — l'avertissement se déclenche pour tout projet lorsqu'il est absent oulocalhost, et le dit ; les projets mono-tenant peuvent l'ignorerDATABASE_URLpointant verslocalhost/127.0.0.1(avertissement — en production, généralement un hôte managé)RUSTANGO_BINDcommençant par127.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_featuresde 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://...
| Flag | Description |
|---|---|
--display-name <name> | Libellé lisible affiché dans les barres latérales d'administration |
--mode schema | database | Mode 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-migrate | Ignore l'application des migrations à scope tenant après le provisionnement |
--backend postgres | mysql | sqlite | Pilote 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
| Flag | Description |
|---|---|
--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 / --deactivate | Met 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
| Verbe | Ce 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|--off | Servir 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
| Verbe | Flags |
|---|---|
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
AppUserplus tard est un changement de schéma ordinaire : réexécutezmakemigrationspour émettre la migrationAddColumn, puismigrate. - Un seul modèle peut correspondre à
rustango_users. Enregistrer à la fois leUserdu framework et votreAppUserrendmakemigrationsambigu — enregistrezAppUserseul. 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")]surOrg/Operatormaintient les changements aux tables partagées hors des migrations par tenant.migrate-tenantsvisite chaque tenant actif et applique uniquement les migrations tenant — les fichiers registry sont ignorés.create-tenantexécute ce même passagemigrate-tenantscontre 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 :
| Champ | Défaut | Objet |
|---|---|---|
max_cached_database_pools | 64 | Plafond du cache de pools. Une fois plein, le prochain tenant non mis en cache échoue (pas d'éviction silencieuse). |
database_pool_max_connections | 16 | max_connections par pool. À garder petit pour qu'un éparpillement de tenants n'épuise pas le max_connections de PG. |
database_pool_min_connections | 0 | Garde 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_timeout | 30s | Durée d'attente de pool.acquire() avant l'erreur PoolTimedOut. |
database_pool_idle_timeout | 10 min | Ferme 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_lifetime | 30 min | Force la rotation des connexions pour que les identifiants louvés via vault soient renouvelés. |
prewarm_active_tenants | false | Si vrai, Server::Builder::serve appelle prewarm_database_tenants() au démarrage. |
Préchauffage au démarrage
Deux façons de le déclencher :
-
Automatique — réglez
prewarm_active_tenants = truesur leTenantPoolsConfigque vous passez àTenantPools::new(...).config(...).Server::Builder::serveexécute le préchauffage avant de se lier au port. -
Verbe CLI —
cargo run -- prewarm-poolsconstruit 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 :
- Utiliser un TLD différent :
127.0.0.1 acme.localhostfonctionne sans délai.localhostest réservé (RFC 6761) et évite mDNS. - Exécuter dnsmasq avec une zone
.localpointant 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
| Verbe | Ce qu'il fait |
|---|---|
makemigrations [name] / --empty <name> | Génère une migration à partir du diff des modèles |
migrate [target] / --dry-run / --squash | Applique les migrations en attente |
downgrade [N] | Annule les N dernières migrations |
showmigrations / status | Liste 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
| Verbe | Ce qu'il fait |
|---|---|
dumpdata | Exporte 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:info | Dump / restauration / inspection natifs |
dbshell | Exé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
| Verbe | Ce qu'il fait |
|---|---|
startapp <name> | Génère un module d'application |
make:viewset / make:serializer / make:form | Génère un ViewSet, un Serializer ou un Form |
make:job / make:scheduled / make:worker / make:middleware / make:notification / make:test | Gé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
| Verbe | Ce 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>] / clearsessions | La vide ; renvoie le nombre de lignes supprimées |
sendtestemail --to <addr> | Envoie un courriel de test fixe via le backend configuré |
Introspection
| Verbe | Ce 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-admin | Cré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 / --version | Informations de build et de version |
docs | Ouvre la documentation |
Utilisateurs et accès T
| Verbe | Ce qu'il fait |
|---|---|
create-superuser / set-superuser | Crée un superutilisateur, ou promeut un utilisateur existant |
create-user / create-operator | Crée un utilisateur de tenant ou un opérateur |
reset-password / change-password | Récupération du mot de passe d'un utilisateur de tenant |
reset-operator-password / change-operator-password | Récupération du mot de passe d'un opérateur |
set-operator-active | Active ou désactive un opérateur |
create-role / assign-role / revoke-role / list-roles | Rôles |
grant-perm / revoke-perm | Permissions par codename |
seed-permissions [--slug <s>] | Initialise les lignes de permission par défaut |
create-api-key | Émet une clé d'API |
Tenants T
| Verbe | Ce qu'il fait |
|---|---|
create-tenant / edit-tenant / list-tenants | Provisionner, éditer, lister |
drop-tenant / purge-tenant | Désactiver (réversible) / détruire (non) |
migrate-tenants / migrate-registry | Applique les migrations à tous les tenants, ou au registre |
migrate-tenant-storage <slug> --to schema|database | Déplace un tenant d'un mode de stockage à l'autre |
add-host / remove-host / list-hosts / set-host-enabled | Routage par hôte |
test-tenant-connection | Vérifie que la base d'un tenant est joignable |
prewarm-pools | Ouvre les pools de tenants avant la première requête |
run-server / runserver | Lance le serveur multi-tenant |
init / init-tenancy / wizard / menu / actions | Points d'entrée de configuration et interactifs |
Audit T
| Verbe | Ce qu'il fait |
|---|---|
audit-log | Lit la piste d'audit |
audit-cleanup | L'élague |
MCP T
Documenté intégralement dans le guide MCP.
| Verbe | Ce qu'il fait |
|---|---|
create-agent / list-agents / rotate-agent-secret | Agents |
create-skill / list-skills / grant-skill / revoke-skill | Skills |
map-skill-permission / unmap-skill-permission | Lie un skill à une permission |
create-user-key / list-user-keys / revoke-user-key | Identifiants MCP par utilisateur |
list-runs / show-run | Historique des exécutions |
