Plugin Files
Plugin Files
Opérations sur les fichiers dans les volumes Unity Catalog de Databricks. Prend en charge le listage, la lecture, le téléchargement, le téléversement, la suppression et la prévisualisation de fichiers, avec gestion intégrée du cache, des réessais et des délais d'expiration via le pipeline d'intercepteurs d'exécution.
Fonctionnalités clés :
- Multi-volume : définissez des volumes nommés (par exemple
uploads,exports) et accédez-y indépendamment - Opérations CRUD sur les fichiers des volumes Unity Catalog
- Téléchargements en streaming avec résolution du type de contenu
- Diffusion brute en ligne avec application d'un type de contenu protégé contre le XSS
- Limites de taille de téléversement appliquées en streaming
- Invalidation automatique du cache lors des opérations d'écriture
- Correspondances de types de contenu personnalisées
- Modes d'authentification par volume : chaque volume peut s'exécuter en tant que service principal (par défaut) ou pour le compte de l'utilisateur final
- Politiques d'accès : des fonctions de politique propres à chaque volume encadrent les opérations de lecture et d'écriture
Utilisation de base
import { createApp, files, server } from "@databricks/appkit";
await createApp({
plugins: [
server(),
files(),
],
});Définissez les variables d'environnement DATABRICKS_VOLUME_* dans votre app.yaml (ou .env). Le plugin les détecte automatiquement au démarrage :
DATABRICKS_VOLUME_UPLOADS=/Volumes/catalog/schema/uploads
DATABRICKS_VOLUME_EXPORTS=/Volumes/catalog/schema/exportsC'est tout — aucune configuration volumes n'est nécessaire. Le suffixe de la variable d'environnement devient la clé du volume (en minuscules) :
| Variable d'environnement | Clé de volume |
|---|---|
DATABRICKS_VOLUME_UPLOADS | uploads |
DATABRICKS_VOLUME_EXPORTS | exports |
Découverte automatique
Le plugin parcourt process.env à la recherche des clés correspondant à DATABRICKS_VOLUME_* et enregistre chacune d'elles comme un volume avec la configuration par défaut {}. Les variables d'environnement dont la valeur est vide ou qui se limitent au préfixe DATABRICKS_VOLUME_ (sans suffixe) sont ignorées.
Sémantique de fusion : les volumes découverts automatiquement sont toujours fusionnés avec ceux configurés explicitement. La configuration explicite l'emporte pour les surcharges propres à un volume (par exemple maxUploadSize), tandis que les volumes issus de la seule découverte automatique reçoivent les paramètres par défaut.
// Surcharges explicites pour uploads ; exports est détecté automatiquement depuis l'environnement
files({
volumes: {
uploads: { maxUploadSize: 100_000_000 },
},
});Cela crée deux volumes (uploads avec une limite de 100 Mo, exports avec les valeurs par défaut), à condition que DATABRICKS_VOLUME_UPLOADS et DATABRICKS_VOLUME_EXPORTS soient tous deux définis.
Configuration
interface IFilesConfig {
/** Volumes nommés à exposer. Chaque clé devient un accesseur de volume. */
volumes?: Record<string, VolumeConfig>;
/** Délai d'expiration des opérations en millisecondes. Remplace les valeurs par défaut propres à chaque tier. */
timeout?: number;
/** Correspondance entre extensions de fichiers et types MIME (prioritaire sur la table intégrée). Héritée par tous les volumes. */
customContentTypes?: Record<string, string>;
/** Taille maximale de téléversement en octets. 5 Go par défaut. Héritée par tous les volumes. */
maxUploadSize?: number;
/**
* Mode d'authentification par défaut au niveau du plugin. Les volumes en héritent
* lorsqu'ils ne définissent pas `VolumeConfig.auth`. Vaut `"service-principal"` par défaut.
*/
auth?: "service-principal" | "on-behalf-of-user";
}
interface VolumeConfig {
/** Politique d'accès de ce volume. */
policy?: FilePolicy;
/** Taille maximale de téléversement en octets pour ce volume. Remplace la valeur par défaut définie au niveau du plugin. */
maxUploadSize?: number;
/** Correspondance entre extensions de fichiers et types MIME pour ce volume. Remplace la valeur par défaut définie au niveau du plugin. */
customContentTypes?: Record<string, string>;
/**
* Mode d'authentification propre au volume. Hérité de `IFilesConfig.auth` s'il n'est pas défini ;
* vaut `"service-principal"` par défaut.
*/
auth?: "service-principal" | "on-behalf-of-user";
}Remplacements par volume
Chaque volume hérite des paramètres maxUploadSize et customContentTypes définis au niveau du plugin, sauf s'ils sont remplacés :
files({
maxUploadSize: 5_000_000_000, // 5 Go par défaut pour tous les volumes
customContentTypes: { ".avro": "application/avro" },
volumes: {
uploads: { maxUploadSize: 100_000_000 }, // limite de 100 Mo pour uploads uniquement
exports: {}, // utilise les valeurs par défaut du plugin
},
});Modes d'authentification
Chaque volume fonctionne selon l'un des deux modes d'authentification. Le mode détermine quelle identité exécute l'appel sous-jacent au SDK Unity Catalog — et donc quel privilège UC s'applique :
| Mode | Identité du SDK | Privilège UC requis sur le volume |
|---|---|---|
"service-principal" (par défaut) | le service principal de l'application | WRITE_VOLUME (ou équivalent en lecture) sur le SP |
"on-behalf-of-user" | l'utilisateur final à l'origine de la requête | WRITE_VOLUME (ou équivalent en lecture) sur l'utilisateur final |
Ordre de résolution
Pour chaque volume, le plugin détermine le mode d'authentification dans cet ordre :
VolumeConfig.auth > IFilesConfig.auth > "service-principal"Définissez IFilesConfig.auth pour modifier en un seul endroit la valeur par défaut de tous les volumes, puis surchargez ce paramètre volume par volume via VolumeConfig.auth.
Mode service principal (par défaut)
Chaque requête HTTP s'exécute sous l'identité du service principal de l'application. L'identité de l'utilisateur final (issue de x-forwarded-user) est toujours transmise à la politique de volume, mais l'appel SDK utilise les identifiants du service principal :
files({
volumes: {
exports: {
// auth est implicite : "service-principal"
policy: files.policy.publicRead(),
},
},
});Utilisez le mode SP pour les ressources partagées, les exports gérés par l'application ou tout cas où vous souhaitez qu'une seule autorisation accordée au SP régisse l'ensemble des accès.
Mode on-behalf-of-user
Chaque requête HTTP s'exécute sous l'identité de l'utilisateur final. Le plugin récupère l'identité de l'utilisateur et son jeton d'accès dans les en-têtes injectés par Databricks Apps (x-forwarded-user et x-forwarded-access-token), puis exécute l'appel au SDK dans runInUserContext :
files({
volumes: {
"user-uploads": {
auth: "on-behalf-of-user",
// La politique voit le véritable utilisateur final (isServicePrincipal: false).
// La politique du volume peut s'utiliser en complément des grants UC.
policy: (action, _resource, user) =>
// N'autoriser que les véritables utilisateurs finaux, jamais le SP.
!user.isServicePrincipal,
},
},
});Utilisez le mode OBO lorsque l'octroi UC par utilisateur est pertinent — par exemple pour appliquer les ACL UC au niveau du SDK, ou pour des pistes d'audit qui doivent imputer l'appel d'API à l'utilisateur final plutôt qu'au SP de l'application.
Comportement en production et en développement
| Environnement | Requête OBO avec un jeton valide | Requête OBO sans l'en-tête x-forwarded-access-token |
|---|---|---|
| Production | S'exécute en tant qu'utilisateur final. | 401 Unauthorized — aucun appel au SDK n'est effectué. |
Développement (NODE_ENV === "development") | S'exécute en tant qu'utilisateur final. | Consigne un avertissement, bascule sur le SP et poursuit. |
Ce repli en mode développement permet aux tests locaux de continuer à fonctionner sans proxy inverse Databricks Apps ; dans les applications déployées, les en-têtes sont toujours injectés.
Limitations
- La méthode
getResourceRequirements()du manifeste du plugin déclareWRITE_VOLUMEsur le service principal pour chaque volume, quel que soit le modeauthdu volume. Pour les volumes OBO, la permission doit en réalité être accordée à l'utilisateur final : communiquez cette information par un autre canal (runbooks de déploiement, documents d'intégration client) tant que le schéma du manifeste du plugin ne comporte pas de champ de portée d'authentification par volume. - Les volumes OBO désactivent entièrement le cache de lecture et de listage. La couche de cache s'indexe sur
getCurrentUserId(): une écriture de l'utilisateur A n'invaliderait donc pas la vue de l'utilisateur B sur le même chemin. Plutôt que de risquer des données obsolètes d'un utilisateur à l'autre, le trafic OBO contourne le cache et récupère des données fraîches à chaque requête. Les volumes SP continuent d'utiliser le cache (une seule tranche indexée sur l'identifiant du service principal).
Modèle de permissions
Le plugin files comporte trois niveaux de contrôle d'accès. Il est essentiel de comprendre comment ils interagissent pour sécuriser votre application :
┌─────────────────────────────────────────────────┐
│ Grants Unity Catalog │
│ WRITE_VOLUME sur le SP (auth: service-principal)│
│ WRITE_VOLUME sur l'utilisateur (auth: on-behalf-of-user)│
├─────────────────────────────────────────────────┤
│ Identité d'exécution │
│ Résolue par volume à partir de VolumeConfig.auth ?? │
│ IFilesConfig.auth ?? "service-principal". │
│ asUser(req) est une surcharge stricte au │
│ niveau du SDK pour l'API programmatique. │
├─────────────────────────────────────────────────┤
│ Politiques de fichiers │
│ Par volume (action, resource, user) → booléen │
│ Seul contrôle applicatif des routes HTTP │
└─────────────────────────────────────────────────┘- Les grants UC contrôlent ce qu'une identité peut faire au niveau de Databricks. L'identité qui doit recevoir le grant dépend du mode d'authentification du volume (voir Modes d'authentification). Pour les volumes SP, c'est le SP qui a besoin de
WRITE_VOLUME(le plugin le déclare dans son manifeste). Pour les volumes OBO, c'est l'utilisateur final qui a besoin deWRITE_VOLUMEsur le volume ; le SP, lui, n'en a pas besoin. - L'identité d'exécution détermine quelles informations d'identification sont utilisées pour l'appel API effectif. Chaque volume se résout soit au service principal, soit à l'utilisateur final, selon son paramètre
auth. L'API programmatique expose égalementasUser(req)pour forcer une exécution par utilisateur, indépendamment du paramètreauthdu volume. - Les politiques de fichiers sont des contrôles applicatifs évalués avant l'appel API. Elles reçoivent un
FilePolicyUserdécrivant l'appelant et décident d'autoriser ou de refuser. Sur les routes HTTP, l'utilisateur de la politique est déterminé par le modeauthdu volume et les en-têtes de la requête — voir la matriceisServicePrincipal. Sur les volumes SP, lorsquex-forwarded-userest absent, la politique reçoit{ id: <sp-id>, isServicePrincipal: true }et décide s'il faut autoriser le trafic du service principal. C'est le seul point de contrôle qui distingue les utilisateurs sur les routes HTTP.
Pour les volumes de type service principal, chaque requête HTTP s'exécute en tant que SP, quel que soit l'utilisateur à l'origine de la requête — retirer le grant UC WRITE_VOLUME d'un utilisateur n'a donc aucun effet sur l'accès HTTP. Les politiques sont le seul moyen de restreindre ce que chaque utilisateur peut faire via votre application.
Pour les volumes on-behalf-of-user, les requêtes s'exécutent sous l'identité de l'utilisateur à l'origine de l'appel — chaque utilisateur doit donc disposer lui-même de WRITE_VOLUME sur le volume, et vous pouvez vous appuyer sur les grants UC en complément des politiques.
Les politiques de fichiers font leur apparition. Les volumes sans politique explicite utilisent désormais publicRead() par défaut, qui refuse toutes les opérations d'écriture (upload, mkdir, delete). Si votre application a besoin d'un accès en écriture, définissez une politique explicite — par exemple files.policy.allowAll() — sur chaque volume concerné.
Politiques d'accès
Associez une politique à un volume pour contrôler les actions autorisées :
import { files } from "@databricks/appkit";
files({
volumes: {
uploads: { policy: files.policy.publicRead() },
},
});Actions
Les politiques reçoivent une chaîne d'action. Voici la liste complète, par catégorie :
| Catégorie | Actions |
|---|---|
| Lecture | list, read, download, raw, exists, metadata, preview |
| Écriture | upload, mkdir, delete |
Politiques intégrées
| Fonction utilitaire | Autorise | Refuse |
|---|---|---|
files.policy.publicRead() | toutes les actions de lecture | toutes les actions d'écriture |
files.policy.allowAll() | tout | rien |
files.policy.denyAll() | rien | tout |
Composer des politiques
Combinez les politiques intégrées et personnalisées à l'aide de trois combinateurs :
files.policy.all(a, b)— ET : toutes les politiques doivent donner leur accord. Court-circuite dès le premier refus.files.policy.any(a, b)— OU : au moins une politique doit donner son accord. Court-circuite dès la première autorisation.files.policy.not(p)— Inverse une politique. Par exemple,not(publicRead())produit une politique en écriture seule (utile pour les volumes d'ingestion ou de dépôt).
// Lecture seule pour les utilisateurs standards, accès complet pour le service principal
files({
volumes: {
shared: {
policy: files.policy.any(
(_action, _resource, user) => !!user.isServicePrincipal,
files.policy.publicRead(),
),
},
},
});Politiques personnalisées
FilePolicy est une fonction (action, resource, user) → boolean | Promise<boolean>, ce qui vous permet d'y intégrer n'importe quelle logique :
import { type FilePolicy, WRITE_ACTIONS } from "@databricks/appkit";
const ADMIN_IDS = ["admin-sp-id", "lead-user-id"];
const adminOnly: FilePolicy = (action, _resource, user) => {
if (WRITE_ACTIONS.has(action)) {
return ADMIN_IDS.includes(user.id);
}
return true; // lecture autorisée pour tous
};
files({
volumes: { reports: { policy: adminOnly } },
});Exemple de politique OBO
Pour les volumes en mode on-behalf-of-user, la politique reçoit isServicePrincipal: false dès lors que la requête s'exécute avec une véritable identité d'utilisateur final. Une approche courante consiste à refuser purement et simplement le trafic des service principals, afin que les appels anonymes (sans en-tête) ne puissent pas atteindre le volume :
import { type FilePolicy } from "@databricks/appkit";
// Refuse tout ce qui s'exécute en tant que service principal — y compris le
// repli en mode dev lorsqu'aucun x-forwarded-access-token n'est fourni. Les
// véritables utilisateurs finaux (avec isServicePrincipal: false) obtiennent
// l'accès configuré.
const usersOnly: FilePolicy = (_action, _resource, user) => {
return user.isServicePrincipal !== true;
};
files({
volumes: {
"user-uploads": {
auth: "on-behalf-of-user",
policy: usersOnly,
},
},
});Vous pouvez la combiner avec n'importe quelle autre politique via files.policy.all(...) pour ajouter un contrôle par action :
files({
volumes: {
"user-uploads": {
auth: "on-behalf-of-user",
policy: files.policy.all(usersOnly, files.policy.publicRead()),
},
},
});Matrice des utilisateurs de politique
Le plugin sélectionne l'utilisateur de politique en fonction du mode auth effectif du volume et des en-têtes de la requête. Tableau complet :
auth du volume | Chemin | En-têtes | isServicePrincipal | Remarques |
|---|---|---|---|---|
service-principal | HTTP | x-forwarded-user présent | false (ou non défini) | Comportement antérieur à l'OBO. La politique voit l'utilisateur final, mais l'appel SDK s'exécute toujours en tant que SP. |
service-principal | HTTP | pas de x-forwarded-user | true | Requête sans en-tête — la politique et le SDK s'exécutent tous deux en tant que SP. |
on-behalf-of-user | HTTP | jeton valide + en-tête utilisateur | false | Exécution réelle sous l'identité de l'utilisateur final. La politique voit l'utilisateur ; l'appel SDK s'exécute également en tant qu'utilisateur. |
on-behalf-of-user | HTTP | jeton manquant, repli de développement | true | Accessible uniquement lorsque NODE_ENV === "development" (en production, renvoie 401). Traité comme du trafic SP. |
| indifférent | asUser(req) par programmation | x-forwarded-user présent | false | asUser extrait l'utilisateur ; l'appel SDK s'exécute en tant qu'utilisateur au sein de runInUserContext. |
| indifférent | Par programmation (sans asUser) | s. o. | true | Aucune requête disponible pour en déduire un utilisateur — exécution en tant que SP. |
Application
- Routes HTTP : la politique est vérifiée avant chaque opération. En cas de refus → réponse JSON
403avecPolicy denied "{action}" on volume "{volumeKey}". - API programmatique : la politique est vérifiée aussi bien sur
appkit.files("vol").list()(identité SP,isServicePrincipal: true) que surappkit.files("vol").asUser(req).list()(identité utilisateur). En cas de refus → une erreurPolicyDeniedErrorest levée. - Aucune politique configurée :
files.policy.publicRead()s'applique par défaut — les actions de lecture sont autorisées, les actions d'écriture refusées. Un avertissement est consigné au démarrage pour vous inciter à définir une politique explicite.
Types de contenu personnalisés
Remplacez ou étendez la table de correspondance extension → MIME intégrée :
files({
volumes: { data: {} },
customContentTypes: {
".avro": "application/avro",
".ndjson": "application/x-ndjson",
},
});Les types MIME dangereux (text/html, text/javascript, application/javascript, application/xhtml+xml, image/svg+xml) sont bloqués afin d'éviter les attaques XSS stockées lorsque les fichiers sont servis en ligne via /raw.
Routes HTTP
Les routes sont montées sur /api/files/*. Chaque route résout le mode d'authentification du volume, puis s'exécute soit en tant que service principal (comportement par défaut), soit en encapsulant l'appel SDK dans runInUserContext pour les volumes OBO. Avant chaque opération, la politique du volume est évaluée pour l'utilisateur de politique résolu — voir la matrice des utilisateurs de politique pour la correspondance exacte. Voir également Politiques d'accès.
| Méthode | Chemin | Query / Corps | Réponse |
|---|---|---|---|
| GET | /volumes | — | { volumes: string[] } |
| GET | /:volumeKey/list | ?path (facultatif) | DirectoryEntry[] |
| GET | /:volumeKey/read | ?path (obligatoire) | Corps text/plain |
| GET | /:volumeKey/download | ?path (obligatoire) | Flux binaire (Content-Disposition: attachment) |
| GET | /:volumeKey/raw | ?path (obligatoire) | Flux binaire (en ligne pour les types sûrs, en pièce jointe pour les types non sûrs) |
| GET | /:volumeKey/exists | ?path (obligatoire) | { exists: boolean } |
| GET | /:volumeKey/metadata | ?path (obligatoire) | FileMetadata |
| GET | /:volumeKey/preview | ?path (obligatoire) | FilePreview |
| POST | /:volumeKey/upload | ?path (obligatoire), corps brut | { success: true } |
| POST | /:volumeKey/mkdir | body.path (obligatoire) | { success: true } |
| DELETE | /:volumeKey | ?path (obligatoire) | { success: true } |
Le paramètre :volumeKey doit correspondre à l'une des clés de volume configurées. Une clé de volume inconnue renvoie une erreur 404 accompagnée de la liste des volumes disponibles.
Validation du chemin
Tous les endpoints qui acceptent un paramètre path appliquent les règles suivantes :
- Le chemin est obligatoire (non vide)
- 4096 caractères maximum
- Aucun octet nul
Sécurité de l'endpoint raw
L'endpoint /:volumeKey/raw renvoie les fichiers en ligne pour un affichage direct dans le navigateur, tout en appliquant des en-têtes de sécurité :
X-Content-Type-Options: nosniffContent-Security-Policy: sandbox- Les types de contenu non sûrs (HTML, JS, SVG) sont forcés en téléchargement via
Content-Disposition: attachment
Valeurs par défaut d'exécution
Chaque opération traverse le pipeline d'intercepteurs avec des valeurs par défaut propres à chaque tier :
| Tier | Cache | Réessais | Délai d'expiration | Opérations |
|---|---|---|---|---|
| Lecture | 60 s | 3x | 30 s | list, read, exists, metadata, preview |
| Téléchargement | aucun | aucun | 30 s | download, raw |
| Écriture | aucun | aucun | 600 s | upload, mkdir, delete |
Les réessais s'appuient sur un backoff exponentiel avec un délai initial de 1 s.
Le délai d'expiration du téléchargement s'applique au démarrage du flux, et non à l'intégralité du transfert.
Isolation du cache
Les clés de cache intègrent la clé du volume, ce qui garantit à chaque volume un cache indépendant. Par exemple, uploads:list et exports:list sont mis en cache séparément.
Les opérations d'écriture (upload, mkdir, delete) invalident automatiquement l'entrée list mise en cache pour le répertoire parent du volume concerné.
API programmatique
L'export du plugin files est une fonction appelable qui accepte une clé de volume et renvoie un VolumeHandle. Ce handle expose directement toutes les méthodes de VolumeAPI, ainsi qu'une méthode asUser(req) permettant d'activer l'exécution par utilisateur.
// Par défaut — s'exécute en tant que service principal, quel que soit le
// paramètre d'authentification du volume (aucune req ne permet d'en déduire un utilisateur).
const entries = await appkit.files("uploads").list();
// asUser(req) — s'exécute en tant qu'utilisateur final, quel que soit le paramètre
// d'authentification du volume. Force les appels SDK à passer par runInUserContext via
// les en-têtes x-forwarded-user / x-forwarded-access-token de la requête.
const entries = await appkit.files("uploads").asUser(req).list();
const content = await appkit.files("exports").asUser(req).read("report.csv");
// Accesseur nommé
const vol = appkit.files.volume("uploads");
await vol.asUser(req).list();asUser(req)
asUser(req) est la méthode prise en charge pour l'exécution programmatique par utilisateur. L'API renvoyée exécute chaque méthode à l'intérieur de runInUserContext, de sorte que le WorkspaceClient sous-jacent est le client à jeton utilisateur : l'appel SDK s'exécute bien en tant qu'utilisateur, et pas seulement la vérification des autorisations.
En production, asUser(req) lève AuthenticationError.missingToken si x-forwarded-user ou x-forwarded-access-token est absent — les deux en-têtes sont nécessaires pour générer un client à portée utilisateur. En développement (NODE_ENV === "development"), un avertissement est journalisé et le repli s'effectue sur le service principal, afin que les tests locaux sans proxy inverse Databricks Apps continuent de fonctionner — ce repli n'applique pas l'enveloppe runInUserContext.
Un volume configuré avec auth: "on-behalf-of-user" ne passe par runInUserContext que sur le chemin de route HTTP, là où les en-têtes de la requête sont disponibles. Un appel programmatique direct — appkit.files("obo-vol").list() — ne dispose d'aucune requête permettant d'en déduire une identité d'utilisateur final : il s'exécute donc avec le client que getWorkspaceClient() résout au point d'appel (généralement le SP au niveau supérieur).
Pour une exécution programmatique par utilisateur, utilisez toujours asUser(req). Le mode auth du volume régit le trafic HTTP ; asUser(req) régit le trafic programmatique.
Méthodes de VolumeAPI
| Méthode | Signature | Retour |
|---|---|---|
list | (directoryPath?: string) | DirectoryEntry[] |
read | (filePath: string, options?: { maxSize?: number }) | string |
download | (filePath: string) | DownloadResponse |
exists | (filePath: string) | boolean |
metadata | (filePath: string) | FileMetadata |
upload | (filePath: string, contents: ReadableStream | Buffer | string, options?: { overwrite?: boolean }) | void |
createDirectory | (directoryPath: string) | void |
delete | (filePath: string) | void |
preview | (filePath: string) | FilePreview |
read()charge l'intégralité du fichier en mémoire sous forme de chaîne. Les fichiers de plus de 10 Mo (valeur par défaut) sont rejetés — utilisezdownload()pour les fichiers volumineux, ou passez{ maxSize: <bytes> }pour modifier cette limite.
Résolution des chemins
Les chemins peuvent être absolus ou relatifs :
- Absolu — commence par
/et doit débuter par/Volumes/(par exemple/Volumes/catalog/schema/vol/data.csv) - Relatif — préfixé par le chemin du volume résolu à partir de la variable d'environnement (par exemple
data.csv→/Volumes/catalog/schema/uploads/data.csv)
La traversée de répertoires (../) est rejetée. Si un chemin relatif est utilisé alors que la variable d'environnement du volume n'est pas définie, une erreur est levée.
La méthode list() sans argument liste la racine du volume.
Types
// Réexporté depuis @databricks/sdk-experimental
type DirectoryEntry = files.DirectoryEntry;
type DownloadResponse = files.DownloadResponse;
interface FileMetadata {
/** Taille du fichier en octets. */
contentLength: number | undefined;
/** Type de contenu MIME du fichier. */
contentType: string | undefined;
/** Horodatage ISO 8601 de la dernière modification. */
lastModified: string | undefined;
}
interface FilePreview extends FileMetadata {
/** Début du contenu textuel, ou null pour les fichiers non textuels. */
textPreview: string | null;
/** Indique si le fichier est détecté comme un format texte. */
isText: boolean;
/** Indique si le fichier est détecté comme un format image. */
isImage: boolean;
}
type FileAction =
| "list" | "read" | "download" | "raw"
| "exists" | "metadata" | "preview"
| "upload" | "mkdir" | "delete";
interface FileResource {
/** Chemin relatif au sein du volume. */
path: string;
/** Clé du volume (par exemple `"uploads"`). */
volume: string;
/** Longueur du contenu en octets — présent uniquement pour les téléversements. */
size?: number;
}
interface FilePolicyUser {
/**
* Identifiant de l'appelant à l'origine de la requête. Pour les requêtes HTTP
* d'utilisateurs finaux, il s'agit de la valeur de l'en-tête `x-forwarded-user` ;
* pour les appels SDK directs et les requêtes HTTP sans en-tête (qui s'exécutent
* en tant que service principal), il s'agit de l'ID du service principal.
*/
id: string;
/**
* `true` lorsque l'appel s'exécute en tant que service principal — qu'il
* s'agisse d'un appel SDK direct (`appKit.files(...)` sans `asUser`), d'une
* requête HTTP sans en-têtes transmis, ou du repli en mode dev pour un volume
* OBO dont le jeton est manquant. Consultez la [matrice des utilisateurs de
* politique](#policy-user-matrix) pour le tableau complet.
*/
isServicePrincipal?: boolean;
}
type FilePolicy = (
action: FileAction,
resource: FileResource,
user: FilePolicyUser,
) => boolean | Promise<boolean>;
interface VolumeConfig {
/** Politique d'accès pour ce volume. */
policy?: FilePolicy;
/** Taille maximale de téléversement en octets pour ce volume. */
maxUploadSize?: number;
/** Correspondance entre extensions de fichiers et types MIME pour ce volume. */
customContentTypes?: Record<string, string>;
/**
* Mode d'authentification propre au volume. Hérite de `IFilesConfig.auth` s'il
* n'est pas défini ; la valeur par défaut est `"service-principal"`.
*/
auth?: "service-principal" | "on-behalf-of-user";
}
interface VolumeAPI {
list(directoryPath?: string): Promise<DirectoryEntry[]>;
read(filePath: string, options?: { maxSize?: number }): Promise<string>;
download(filePath: string): Promise<DownloadResponse>;
exists(filePath: string): Promise<boolean>;
metadata(filePath: string): Promise<FileMetadata>;
upload(filePath: string, contents: ReadableStream | Buffer | string, options?: { overwrite?: boolean }): Promise<void>;
createDirectory(directoryPath: string): Promise<void>;
delete(filePath: string): Promise<void>;
preview(filePath: string): Promise<FilePreview>;
}
/**
* Handle de volume : toutes les méthodes de VolumeAPI (exécutées par défaut en
* tant que service principal) + asUser() pour forcer une exécution par
* utilisateur au niveau du SDK.
*/
type VolumeHandle = VolumeAPI & {
asUser: (req: Request) => VolumeAPI;
};Résolution du type de contenu
contentTypeFromPath(filePath, reported?, customTypes?) détermine le type MIME d'un fichier :
- Consulter d'abord la table
customContentTypes(si elle est configurée). - Comparer l'extension du fichier à la table intégrée.
- À défaut, utiliser le type signalé par le serveur, ou
application/octet-stream.
Extensions intégrées : .png, .jpg, .jpeg, .gif, .webp, .svg, .bmp, .ico, .html, .css, .js, .ts, .py, .txt, .md, .csv, .json, .jsonl, .xml, .yaml, .yml, .sql, .pdf, .ipynb, .parquet, .zip, .gz.
Contexte utilisateur
Les routes HTTP s'exécutent soit en tant que service principal, soit en tant qu'utilisateur final, selon le mode d'authentification du volume :
- Volumes en mode service principal (par défaut) : les identifiants Databricks du SP sont utilisés pour l'appel d'API. L'identité de l'utilisateur est extraite de l'en-tête
x-forwarded-useret transmise à la politique d'accès du volume pour autorisation, mais l'appel du SDK s'exécute malgré tout en tant que SP. Lorsque l'en-tête est absent, la politique reçoit{ id: <sp-id>, isServicePrincipal: true }et décide d'autoriser ou non l'appel — en pratique, ce cas ne se produit qu'en développement sans proxy inverse, ou lorsqu'un proxy en amont est mal configuré, puisque les runtimes réels de Databricks Apps transmettent toujours l'en-tête. Les grants UC accordés au SP déterminent les opérations possibles. - Volumes en mode on-behalf-of-user : le jeton d'accès de l'utilisateur final (issu de
x-forwarded-access-token) sert à créer le client SDK, de sorte que l'appel d'API s'exécute avec l'identité de l'utilisateur. La politique comme le SDK voient l'utilisateur. Les grants UC accordés à l'utilisateur final déterminent les opérations possibles. En production, les requêtes dépourvues de jeton renvoient401; en développement (NODE_ENV === "development"), elles se rabattent sur le SP avec un avertissement.
L'API programmatique renvoie un VolumeHandle qui expose directement toutes les méthodes de VolumeAPI, ainsi qu'une méthode asUser(req) permettant de forcer l'exécution par utilisateur. Appeler une méthode sans asUser() exécute la politique et l'appel du SDK en tant que SP. asUser(req) constitue une surcharge stricte au niveau du SDK : elle force chaque appel ultérieur à s'exécuter en tant qu'utilisateur final dans runInUserContext, quel que soit le paramètre auth du volume. En production, asUser(req) lève AuthenticationError.missingToken si x-forwarded-user ou x-forwarded-access-token est absent — les deux en-têtes sont requis. En développement, la méthode se rabat sur le service principal, ce qui permet de continuer à tester en local sans proxy inverse.
Exigences en matière de ressources
Les ressources de volume sont déclarées dynamiquement via getResourceRequirements(config), à partir des volumes découverts et configurés. Chaque clé de volume génère une ressource requise assortie de la permission WRITE_VOLUME et d'une variable d'environnement DATABRICKS_VOLUME_{KEY_UPPERCASE}.
Par exemple, si DATABRICKS_VOLUME_UPLOADS et DATABRICKS_VOLUME_EXPORTS sont définies, l'appel à files() génère deux ressources de volume requises, validées au démarrage — sans qu'aucune configuration volumes explicite ne soit nécessaire.
Le manifeste déclare l'octroi au profit du service principal. Pour les volumes OBO (auth: "on-behalf-of-user"), la permission doit en réalité être accordée à l'utilisateur final : précisez-le par un autre canal dans votre documentation de déploiement, tant que le schéma du manifeste ne comporte pas de champ de portée d'authentification par volume.
Réponses d'erreur
Toutes les erreurs renvoient du JSON :
{
"error": "Human-readable message",
"plugin": "files"
}| Statut | Description |
|---|---|
| 400 | Paramètre path manquant ou invalide |
| 403 | Action "{action}" refusée par la politique sur le volume "{volumeKey}" |
| 404 | Clé de volume inconnue |
| 413 | Le téléversement dépasse maxUploadSize |
| 500 | Échec de l'opération (SDK, réseau, service en amont ou erreur non gérée) |
Composants frontend
Le package @databricks/appkit-ui fournit des composants React prêts à l'emploi pour créer un explorateur de fichiers :
FileBrowser
Un ensemble de composants composables pour parcourir, prévisualiser et gérer les fichiers d'un volume Unity Catalog :
import {
DirectoryList,
FileBreadcrumb,
FilePreviewPanel,
} from "@databricks/appkit-ui/react";
function FileBrowserPage() {
return (
<div style={{ display: "flex", gap: 16 }}>
<div style={{ flex: 1 }}>
<FileBreadcrumb
rootLabel="uploads"
segments={["data"]}
onNavigateToRoot={() => {}}
onNavigateToSegment={() => {}}
/>
<DirectoryList
entries={[]}
onEntryClick={() => {}}
resolveEntryPath={(entry) => entry.path ?? ""}
/>
</div>
<FilePreviewPanel selectedFile={null} preview={null} />
</div>
);
}Consultez la référence des composants Files (UC) pour l'API complète des propriétés.