Accéder au contenu principal

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/exports

C'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'environnementClé de volume
DATABRICKS_VOLUME_UPLOADSuploads
DATABRICKS_VOLUME_EXPORTSexports

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 :

ModeIdentité du SDKPrivilège UC requis sur le volume
"service-principal" (par défaut)le service principal de l'applicationWRITE_VOLUME (ou équivalent en lecture) sur le SP
"on-behalf-of-user"l'utilisateur final à l'origine de la requêteWRITE_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

EnvironnementRequête OBO avec un jeton valideRequête OBO sans l'en-tête x-forwarded-access-token
ProductionS'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éclare WRITE_VOLUME sur le service principal pour chaque volume, quel que soit le mode auth du 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 de WRITE_VOLUME sur 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 également asUser(req) pour forcer une exécution par utilisateur, indépendamment du paramètre auth du volume.
  • Les politiques de fichiers sont des contrôles applicatifs évalués avant l'appel API. Elles reçoivent un FilePolicyUser dé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 mode auth du volume et les en-têtes de la requête — voir la matrice isServicePrincipal. Sur les volumes SP, lorsque x-forwarded-user est 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.
warning

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.

Nouveauté de la v0.21.0

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égorieActions
Lecturelist, read, download, raw, exists, metadata, preview
Écritureupload, mkdir, delete

Politiques intégrées

Fonction utilitaireAutoriseRefuse
files.policy.publicRead()toutes les actions de lecturetoutes les actions d'écriture
files.policy.allowAll()toutrien
files.policy.denyAll()rientout

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 volumeCheminEn-têtesisServicePrincipalRemarques
service-principalHTTPx-forwarded-user présentfalse (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-principalHTTPpas de x-forwarded-usertrueRequête sans en-tête — la politique et le SDK s'exécutent tous deux en tant que SP.
on-behalf-of-userHTTPjeton valide + en-tête utilisateurfalseExé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-userHTTPjeton manquant, repli de développementtrueAccessible uniquement lorsque NODE_ENV === "development" (en production, renvoie 401). Traité comme du trafic SP.
indifférentasUser(req) par programmationx-forwarded-user présentfalseasUser extrait l'utilisateur ; l'appel SDK s'exécute en tant qu'utilisateur au sein de runInUserContext.
indifférentPar programmation (sans asUser)s. o.trueAucune 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 403 avec Policy 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 sur appkit.files("vol").asUser(req).list() (identité utilisateur). En cas de refus → une erreur PolicyDeniedError est 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éthodeCheminQuery / CorpsRé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/mkdirbody.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: nosniff
  • Content-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 :

TierCacheRéessaisDélai d'expirationOpérations
Lecture60 s3x30 slist, read, exists, metadata, preview
Téléchargementaucunaucun30 sdownload, raw
Écritureaucunaucun600 supload, 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.

OBO programmatique sans `asUser(req)`

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éthodeSignatureRetour
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 — utilisez download() 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 :

  1. Consulter d'abord la table customContentTypes (si elle est configurée).
  2. Comparer l'extension du fichier à la table intégrée.
  3. À 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-user et 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 renvoient 401 ; 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"
}
StatutDescription
400Paramètre path manquant ou invalide
403Action "{action}" refusée par la politique sur le volume "{volumeKey}"
404Clé de volume inconnue
413Le 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.

Databricks Developer Hub

Prêt à lancer votre prochaine application agentique en quelques minutes ?

Lire la documentation