Ir al contenido principal

Plugin de archivos

Plugin de archivos

Operaciones con archivos sobre volúmenes de Unity Catalog de Databricks. Permite listar, leer, descargar, subir, eliminar y previsualizar archivos, con caché, reintentos y gestión de tiempos de espera integrados mediante el pipeline de interceptores de ejecución.

Características principales:

  • Multivolumen: define volúmenes con nombre (por ejemplo, uploads, exports) y accede a ellos de forma independiente
  • Operaciones CRUD sobre archivos de volúmenes de Unity Catalog
  • Descargas en streaming con resolución del tipo de contenido
  • Entrega de contenido sin procesar en línea con tipos de contenido seguros frente a XSS
  • Límites de tamaño de subida aplicados durante el streaming
  • Invalidación automática de la caché en las operaciones de escritura
  • Asignaciones personalizadas de tipos de contenido
  • Modos de autenticación por volumen: cada volumen puede ejecutarse como el service principal (opción predeterminada) o en nombre del usuario final
  • Políticas de acceso: funciones de política por volumen que controlan las operaciones de lectura y escritura

Uso básico

import { createApp, files, server } from "@databricks/appkit";

await createApp({
  plugins: [
    server(),
    files(),
  ],
});

Define las variables de entorno DATABRICKS_VOLUME_* en tu app.yaml (o .env). El plugin las detecta automáticamente al arrancar:

DATABRICKS_VOLUME_UPLOADS=/Volumes/catalog/schema/uploads
DATABRICKS_VOLUME_EXPORTS=/Volumes/catalog/schema/exports

Eso es todo: no hace falta configurar volumes. El sufijo de la variable de entorno pasa a ser la clave del volumen (en minúsculas):

Variable de entornoClave del volumen
DATABRICKS_VOLUME_UPLOADSuploads
DATABRICKS_VOLUME_EXPORTSexports

Descubrimiento automático

El plugin recorre process.env en busca de claves que coincidan con DATABRICKS_VOLUME_* y registra cada una como un volumen con la configuración predeterminada {}. Se omiten las variables de entorno con valor vacío o que solo tengan el prefijo DATABRICKS_VOLUME_ (sin sufijo).

Semántica de combinación: los volúmenes descubiertos automáticamente siempre se combinan con los configurados de forma explícita. La configuración explícita prevalece en las anulaciones por volumen (por ejemplo, maxUploadSize), mientras que los volúmenes detectados únicamente por descubrimiento reciben la configuración predeterminada.

// Overrides explícitos para uploads; exports se detecta automáticamente desde el entorno
files({
  volumes: {
    uploads: { maxUploadSize: 100_000_000 },
  },
});

Esto genera dos volúmenes (uploads con un límite de 100 MB y exports con los valores predeterminados), suponiendo que tanto DATABRICKS_VOLUME_UPLOADS como DATABRICKS_VOLUME_EXPORTS estén definidos.

Configuración

interface IFilesConfig {
  /** Volúmenes con nombre que se exponen. Cada clave se convierte en un accesor de volumen. */
  volumes?: Record<string, VolumeConfig>;
  /** Tiempo de espera de la operación en milisegundos. Anula los valores predeterminados de cada nivel. */
  timeout?: number;
  /** Mapa de extensiones de archivo a tipos MIME (tiene prioridad sobre el mapa integrado). Lo heredan todos los volúmenes. */
  customContentTypes?: Record<string, string>;
  /** Tamaño máximo de carga en bytes. El valor predeterminado es 5 GB. Lo heredan todos los volúmenes. */
  maxUploadSize?: number;
  /**
   * Modo de autenticación predeterminado a nivel de plugin. Los volúmenes lo heredan
   * cuando no definen `VolumeConfig.auth`. El valor predeterminado es `"service-principal"`.
   */
  auth?: "service-principal" | "on-behalf-of-user";
}

interface VolumeConfig {
  /** Política de acceso para este volumen. */
  policy?: FilePolicy;
  /** Tamaño máximo de carga en bytes para este volumen. Anula el valor predeterminado a nivel de plugin. */
  maxUploadSize?: number;
  /** Mapa de extensiones de archivo a tipos MIME para este volumen. Anula el valor predeterminado a nivel de plugin. */
  customContentTypes?: Record<string, string>;
  /**
   * Modo de autenticación por volumen. Si no se define, se hereda de `IFilesConfig.auth`;
   * el valor predeterminado es `"service-principal"`.
   */
  auth?: "service-principal" | "on-behalf-of-user";
}

Anulaciones por volumen

Cada volumen hereda los valores de maxUploadSize y customContentTypes definidos a nivel de plugin, salvo que se anulen:

files({
  maxUploadSize: 5_000_000_000, // 5 GB por defecto para todos los volúmenes
  customContentTypes: { ".avro": "application/avro" },
  volumes: {
    uploads: { maxUploadSize: 100_000_000 }, // Límite de 100 MB solo para uploads
    exports: {},                              // usa los valores por defecto del plugin
  },
});

Modos de autenticación

Cada volumen funciona en uno de dos modos de autenticación. El modo determina qué identidad ejecuta la llamada subyacente al SDK de Unity Catalog y, por lo tanto, qué permiso de UC se aplica:

ModoIdentidad del SDKPermiso de UC requerido sobre el volumen
"service-principal" (predeterminado)el service principal de la aplicaciónWRITE_VOLUME (o su equivalente de lectura) sobre el SP
"on-behalf-of-user"el usuario final que origina la solicitudWRITE_VOLUME (o su equivalente de lectura) sobre el usuario final

Orden de resolución

Para cada volumen, el plugin resuelve el modo de autenticación en este orden:

VolumeConfig.auth > IFilesConfig.auth > "service-principal"

Define IFilesConfig.auth para cambiar el valor predeterminado de todos los volúmenes en un solo lugar y anula volúmenes individuales mediante VolumeConfig.auth.

Modo service principal (predeterminado)

Cada solicitud HTTP se ejecuta como el service principal de la aplicación. La identidad del usuario final (obtenida de x-forwarded-user) se sigue pasando a la política del volumen, pero la llamada al SDK usa las credenciales del SP:

files({
  volumes: {
    exports: {
      // auth es implícito: "service-principal"
      policy: files.policy.publicRead(),
    },
  },
});

Usa el modo SP para recursos compartidos, exportaciones gestionadas por la app o cualquier caso en el que quieras que un único permiso otorgado al SP controle todo el acceso.

Modo on-behalf-of-user

Cada solicitud HTTP se ejecuta como el usuario final. El plugin obtiene la identidad y el token de acceso del usuario a partir de las cabeceras que inyecta Databricks Apps (x-forwarded-user y x-forwarded-access-token) y ejecuta la llamada al SDK dentro de runInUserContext:

files({
  volumes: {
    "user-uploads": {
      auth: "on-behalf-of-user",
      // La política ve al usuario final real (isServicePrincipal: false).
      // Puedes usar la política del volumen además de los grants de UC.
      policy: (action, _resource, user) =>
        // Permitir solo a usuarios finales reales, nunca al SP.
        !user.isServicePrincipal,
    },
  },
});

Usa el modo OBO cuando el permiso de UC por usuario sea relevante; por ejemplo, para aplicar las ACL de UC en la capa del SDK o para registros de auditoría que deban atribuir la llamada a la API al usuario final en lugar de al SP de la app.

Comportamiento en producción frente a desarrollo

EntornoSolicitud OBO con token válidoSolicitud OBO sin x-forwarded-access-token
ProducciónSe ejecuta como el usuario final.401 Unauthorized: no se realiza ninguna llamada al SDK.
Desarrollo (NODE_ENV === "development")Se ejecuta como el usuario final.Registra una advertencia, recurre al SP y continúa.

El mecanismo de respaldo en modo de desarrollo existe para que las pruebas locales sigan funcionando sin un proxy inverso de Databricks Apps; en las aplicaciones desplegadas, las cabeceras siempre se inyectan.

Limitaciones

  • El método getResourceRequirements() del manifiesto del plugin declara WRITE_VOLUME sobre el service principal para todos los volúmenes, sin importar el modo auth de cada uno. En los volúmenes OBO, el permiso que realmente se necesita recae sobre el usuario final: comunícalo por otros canales (runbooks de despliegue, documentación de incorporación de clientes) hasta que el esquema del manifiesto del plugin incluya un campo de ámbito de autenticación por volumen.
  • Los volúmenes OBO deshabilitan por completo la caché de lectura y listado. La capa de caché se indexa por getCurrentUserId(), de modo que una escritura del usuario A no invalidaría la vista que el usuario B tiene de esa misma ruta; para evitar el riesgo de datos obsoletos entre usuarios, el tráfico OBO omite la caché y obtiene datos actualizados en cada solicitud. Los volúmenes SP sí usan caché (un único segmento indexado por el id del SP).

Modelo de permisos

El plugin de archivos tiene tres capas de control de acceso. Comprender cómo interactúan entre sí es fundamental para proteger tu app:

┌─────────────────────────────────────────────────┐ │ Grants de Unity Catalog │ │ WRITE_VOLUME en el SP (auth: service-principal)│ │ WRITE_VOLUME en el usuario (auth: on-behalf-of-user)│ ├─────────────────────────────────────────────────┤ │ Identidad de ejecución │ │ Se resuelve por volumen: VolumeConfig.auth ?? │ │ IFilesConfig.auth ?? "service-principal". │ │ asUser(req) es una anulación total a nivel │ │ de SDK para la API programática. │ ├─────────────────────────────────────────────────┤ │ Políticas de archivos │ │ Por volumen (action, resource, user) → boolean │ │ Único control a nivel de app en rutas HTTP │ └─────────────────────────────────────────────────┘
  • Los grants de UC controlan lo que una identidad puede hacer a nivel de Databricks. Qué identidad necesita el grant depende del modo de autenticación del volumen (consulta Modos de autenticación). Para volúmenes de SP, el SP necesita WRITE_VOLUME (el plugin lo declara en su manifiesto). Para volúmenes OBO, es el usuario final quien necesita WRITE_VOLUME sobre el volumen; el SP no.
  • La identidad de ejecución determina qué credenciales se usan en la llamada real a la API. Cada volumen se resuelve al service principal o al usuario final, según su configuración de auth. La API programática también expone asUser(req) para forzar la ejecución por usuario con independencia del auth del volumen.
  • Las políticas de archivos son comprobaciones a nivel de aplicación que se evalúan antes de la llamada a la API. Reciben un FilePolicyUser que describe a quien realiza la llamada y deciden si permitir o denegar. En las rutas HTTP, el usuario de la política se selecciona según el modo auth del volumen y las cabeceras de la solicitud; consulta la matriz de isServicePrincipal. En volúmenes de SP, cuando falta x-forwarded-user, la política recibe { id: <sp-id>, isServicePrincipal: true } y decide si permite el tráfico del service principal. Este es el único control que distingue entre usuarios en las rutas HTTP.
warning

En los volúmenes de service principal, toda solicitud HTTP se ejecuta como el SP sin importar qué usuario la haya realizado, por lo que quitar el grant de UC WRITE_VOLUME a un usuario no tiene efecto sobre el acceso HTTP. Las políticas son la forma de restringir lo que cada usuario puede hacer a través de tu aplicación.

En los volúmenes on-behalf-of-user, las solicitudes se ejecutan como el usuario que las realiza, por lo que cada usuario debe tener WRITE_VOLUME sobre el volumen y puedes apoyarte en los grants de UC además de en las políticas.

Novedad en v0.21.0

Las políticas de archivos son una novedad. Los volúmenes sin una política explícita ahora usan publicRead() de forma predeterminada, lo que deniega todas las operaciones de escritura (upload, mkdir, delete). Si tu aplicación depende del acceso de escritura, establece una política explícita —por ejemplo files.policy.allowAll()— en cada volumen que lo necesite.

Políticas de acceso

Adjunta una política a un volumen para controlar qué acciones se permiten:

import { files } from "@databricks/appkit";

files({
  volumes: {
    uploads: { policy: files.policy.publicRead() },
  },
});

Acciones

Las políticas reciben una cadena de acción. Esta es la lista completa, agrupada por categoría:

CategoríaAcciones
Lecturalist, read, download, raw, exists, metadata, preview
Escrituraupload, mkdir, delete

Políticas integradas

Función auxiliarPermiteDeniega
files.policy.publicRead()todas las acciones de lecturatodas las acciones de escritura
files.policy.allowAll()todonada
files.policy.denyAll()nadatodo

Composición de políticas

Combina políticas integradas y personalizadas con tres combinadores:

  • files.policy.all(a, b) — AND: todas las políticas deben permitir el acceso. Se evalúa en cortocircuito ante la primera denegación.
  • files.policy.any(a, b) — OR: al menos una política debe permitir el acceso. Se evalúa en cortocircuito ante la primera autorización.
  • files.policy.not(p) — Invierte una política. Por ejemplo, not(publicRead()) genera una política de solo escritura (útil para volúmenes de ingesta o de tipo buzón).
// Solo lectura para usuarios normales, acceso total para el service principal
files({
  volumes: {
    shared: {
      policy: files.policy.any(
        (_action, _resource, user) => !!user.isServicePrincipal,
        files.policy.publicRead(),
      ),
    },
  },
});

Políticas personalizadas

FilePolicy es una función (action, resource, user) → boolean | Promise<boolean>, por lo que puedes incluir cualquier lógica en línea:

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; // lecturas permitidas para todos
};

files({
  volumes: { reports: { policy: adminOnly } },
});

Ejemplo de política OBO

En los volúmenes on-behalf-of-user, la política recibe isServicePrincipal: false siempre que la solicitud se ejecuta con la identidad de un usuario real. Un patrón habitual consiste en denegar de plano el tráfico de service principals, de modo que las llamadas anónimas (sin encabezado) no puedan llegar al volumen:

import { type FilePolicy } from "@databricks/appkit";

// Denegar todo lo que se ejecute como service principal, incluido el
// mecanismo alternativo del modo de desarrollo cuando no se proporcionó
// x-forwarded-access-token. Los usuarios finales reales
// (con isServicePrincipal: false) obtienen el acceso configurado.
const usersOnly: FilePolicy = (_action, _resource, user) => {
  return user.isServicePrincipal !== true;
};

files({
  volumes: {
    "user-uploads": {
      auth: "on-behalf-of-user",
      policy: usersOnly,
    },
  },
});

Puedes componerla con cualquier otra política mediante files.policy.all(...) para añadir control por acción:

files({
  volumes: {
    "user-uploads": {
      auth: "on-behalf-of-user",
      policy: files.policy.all(usersOnly, files.policy.publicRead()),
    },
  },
});

Matriz de usuarios de la política

El plugin selecciona el usuario de la política según el modo auth efectivo del volumen y las cabeceras de la solicitud. La tabla completa:

auth del volumenRutaCabecerasisServicePrincipalNotas
service-principalHTTPx-forwarded-user presentefalse (o sin definir)Comportamiento previo a OBO. La política ve al usuario final, pero la llamada al SDK se sigue ejecutando como el SP.
service-principalHTTPsin x-forwarded-usertrueSolicitud sin cabeceras: tanto la política como el SDK se ejecutan como el SP.
on-behalf-of-userHTTPtoken válido + cabecera de usuariofalseEjecución real como usuario final. La política ve al usuario y la llamada al SDK también se ejecuta como el usuario.
on-behalf-of-userHTTPsin token, respaldo de desarrollotrueSolo accesible cuando NODE_ENV === "development" (en producción devuelve 401). Se trata como tráfico del SP.
cualquieraasUser(req) programáticox-forwarded-user presentefalseasUser extrae al usuario; la llamada al SDK se ejecuta como el usuario dentro de runInUserContext.
cualquieraProgramático (sin asUser)n/dtrueNo hay ninguna solicitud de la que derivar un usuario: se ejecuta como el SP.

Aplicación

  • Rutas HTTP: la política se comprueba antes de cada operación. Si se deniega → respuesta JSON 403 con Policy denied "{action}" on volume "{volumeKey}".
  • API programática: la política se comprueba tanto en appkit.files("vol").list() (identidad del SP, isServicePrincipal: true) como en appkit.files("vol").asUser(req).list() (identidad del usuario). Si se deniega → se lanza PolicyDeniedError.
  • Sin política configurada: se aplica files.policy.publicRead() de forma predeterminada; se permiten las acciones de lectura y se deniegan las de escritura. Al iniciar se registra una advertencia que recomienda definir una política explícita.

Tipos de contenido personalizados

Sobrescribe o amplía el mapa integrado de extensión → MIME:

files({
  volumes: { data: {} },
  customContentTypes: {
    ".avro": "application/avro",
    ".ndjson": "application/x-ndjson",
  },
});

Los tipos MIME peligrosos (text/html, text/javascript, application/javascript, application/xhtml+xml, image/svg+xml) se bloquean para evitar XSS almacenado cuando los archivos se sirven en línea mediante /raw.

Rutas HTTP

Las rutas se montan en /api/files/*. Cada ruta resuelve el modo de autenticación del volumen y, o bien se ejecuta como el service principal (opción predeterminada), o bien envuelve la llamada al SDK en runInUserContext para los volúmenes OBO. Antes de cada operación, la política del volumen se evalúa sobre el usuario de política resuelto; consulta la matriz de usuarios de política para ver la correspondencia exacta. Consulta también Políticas de acceso.

MétodoRutaQuery / CuerpoRespuesta
GET/volumes{ volumes: string[] }
GET/:volumeKey/list?path (opcional)DirectoryEntry[]
GET/:volumeKey/read?path (obligatorio)Cuerpo text/plain
GET/:volumeKey/download?path (obligatorio)Flujo binario (Content-Disposition: attachment)
GET/:volumeKey/raw?path (obligatorio)Flujo binario (en línea para tipos seguros, adjunto para los no seguros)
GET/:volumeKey/exists?path (obligatorio){ exists: boolean }
GET/:volumeKey/metadata?path (obligatorio)FileMetadata
GET/:volumeKey/preview?path (obligatorio)FilePreview
POST/:volumeKey/upload?path (obligatorio), cuerpo sin procesar{ success: true }
POST/:volumeKey/mkdirbody.path (obligatorio){ success: true }
DELETE/:volumeKey?path (obligatorio){ success: true }

El parameter :volumeKey debe coincidir con una de las claves de volumen configuradas. Las claves de volumen desconocidas devuelven un 404 junto con la lista de volúmenes disponibles.

Validación de rutas

Todos los endpoints que aceptan un parámetro path aplican estas reglas:

  • La ruta es obligatoria (no puede estar vacía)
  • Máximo 4096 caracteres
  • Sin bytes nulos

Seguridad del endpoint raw

El endpoint /:volumeKey/raw entrega los archivos en línea para su visualización en el navegador, pero aplica cabeceras de seguridad:

  • X-Content-Type-Options: nosniff
  • Content-Security-Policy: sandbox
  • Los tipos de contenido no seguros (HTML, JS, SVG) se fuerzan a descargarse mediante Content-Disposition: attachment

Valores predeterminados de ejecución

Todas las operaciones se ejecutan a través del pipeline de interceptores con valores predeterminados específicos de cada nivel:

NivelCachéReintentosTiempo de esperaOperaciones
Lectura60 s3x30 slist, read, exists, metadata, preview
Descarganinguna3x30 sdownload, raw
Escrituraningunaninguno600 supload, mkdir, delete

Los reintentos usan retroceso exponencial con un retraso inicial de 1 s.

El tiempo de espera de descarga se aplica al inicio del flujo, no a la transferencia completa.

Aislamiento de caché

Las claves de caché incluyen la clave del volumen, lo que garantiza que cada volumen tenga su propia caché. Por ejemplo, uploads:list y exports:list se almacenan en caché por separado.

Las operaciones de escritura (upload, mkdir, delete) invalidan automáticamente la entrada list almacenada en caché correspondiente al directorio padre del volumen afectado.

API programática

La exportación del plugin files es una función invocable que acepta una clave de volumen y devuelve un VolumeHandle. El handle expone directamente todos los métodos de VolumeAPI y un método asUser(req) para habilitar la ejecución por usuario.

// Por defecto — se ejecuta como el service principal, sin importar la
// configuración de autenticación del volumen (no hay req del que derivar un usuario).
const entries = await appkit.files("uploads").list();

// asUser(req) — se ejecuta como el usuario final, sin importar la configuración
// de autenticación del volumen. Obliga a que las llamadas del SDK pasen por
// runInUserContext usando las cabeceras x-forwarded-user /
// x-forwarded-access-token de la solicitud.
const entries = await appkit.files("uploads").asUser(req).list();
const content = await appkit.files("exports").asUser(req).read("report.csv");

// Accesor con nombre
const vol = appkit.files.volume("uploads");
await vol.asUser(req).list();

asUser(req)

asUser(req) es la vía admitida para la ejecución programática por usuario. La API devuelta ejecuta cada método dentro de runInUserContext, de modo que el WorkspaceClient subyacente es el cliente con token de usuario: la llamada al SDK se ejecuta como el usuario, no solo la comprobación de la política.

En producción, asUser(req) lanza AuthenticationError.missingToken cuando falta x-forwarded-user o x-forwarded-access-token: ambas cabeceras son necesarias para emitir un cliente con alcance de usuario. En desarrollo (NODE_ENV === "development") registra una advertencia y recurre al service principal, de modo que las pruebas locales sin un proxy inverso de Databricks Apps siguen funcionando; en ese caso se omite el envoltorio de runInUserContext.

OBO programático sin `asUser(req)`

Un volumen configurado con auth: "on-behalf-of-user" solo pasa por runInUserContext en la ruta HTTP, donde las cabeceras de la solicitud están disponibles. Una llamada programática directa —appkit.files("obo-vol").list()— no dispone de ninguna solicitud de la que derivar la identidad del usuario final, por lo que se ejecuta con el cliente que getWorkspaceClient() resuelva en el punto de la llamada (normalmente el SP en el nivel superior).

Para la ejecución programática por usuario, usa siempre asUser(req). El modo auth del volumen controla el tráfico HTTP; asUser(req) controla el tráfico programático.

Métodos de VolumeAPI

MétodoFirmaDevuelve
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() carga el archivo completo en memoria como una cadena. Los archivos de más de 10 MB (valor predeterminado) se rechazan: usa download() para archivos grandes o pasa { maxSize: <bytes> } para modificar este límite.

Resolución de rutas

Las rutas pueden ser absolutas o relativas:

  • Absolutas — empiezan por / y deben comenzar con /Volumes/ (por ejemplo, /Volumes/catalog/schema/vol/data.csv)
  • Relativas — se les antepone la ruta del volumen resuelta a partir de la variable de entorno (por ejemplo, data.csv/Volumes/catalog/schema/uploads/data.csv)

El salto de directorios (../) se rechaza. Si se usa una ruta relativa y la variable de entorno del volumen no está definida, se lanza un error.

El método list() sin argumentos muestra el contenido de la raíz del volumen.

Tipos

// Reexportado desde @databricks/sdk-experimental
type DirectoryEntry = files.DirectoryEntry;
type DownloadResponse = files.DownloadResponse;

interface FileMetadata {
  /** Tamaño del archivo en bytes. */
  contentLength: number | undefined;
  /** Tipo de contenido MIME del archivo. */
  contentType: string | undefined;
  /** Marca de tiempo ISO 8601 de la última modificación. */
  lastModified: string | undefined;
}

interface FilePreview extends FileMetadata {
  /** Primera parte del contenido de texto, o null si el archivo no es de texto. */
  textPreview: string | null;
  /** Indica si el archivo se detecta como formato de texto. */
  isText: boolean;
  /** Indica si el archivo se detecta como formato de imagen. */
  isImage: boolean;
}

type FileAction =
  | "list" | "read" | "download" | "raw"
  | "exists" | "metadata" | "preview"
  | "upload" | "mkdir" | "delete";

interface FileResource {
  /** Ruta relativa dentro del volumen. */
  path: string;
  /** La clave del volumen (por ejemplo, `"uploads"`). */
  volume: string;
  /** Longitud del contenido en bytes: solo presente en las cargas. */
  size?: number;
}

interface FilePolicyUser {
  /**
   * Identificador de quien realiza la llamada. En las solicitudes HTTP de
   * usuarios finales es el valor de la cabecera `x-forwarded-user`; en las
   * llamadas directas del SDK y en las solicitudes HTTP sin cabeceras (que se
   * ejecutan como el service principal), es el ID del service principal.
   */
  id: string;
  /**
   * `true` cuando la llamada se ejecuta como el service principal: ya sea una
   * llamada directa del SDK (`appKit.files(...)` sin `asUser`), una solicitud
   * HTTP sin cabeceras reenviadas o el mecanismo alternativo del modo de
   * desarrollo para un volumen OBO al que le falta el token. Consulta la
   * [matriz de usuarios de políticas](#policy-user-matrix) para ver la tabla completa.
   */
  isServicePrincipal?: boolean;
}

type FilePolicy = (
  action: FileAction,
  resource: FileResource,
  user: FilePolicyUser,
) => boolean | Promise<boolean>;

interface VolumeConfig {
  /** Política de acceso para este volumen. */
  policy?: FilePolicy;
  /** Tamaño máximo de carga en bytes para este volumen. */
  maxUploadSize?: number;
  /** Mapa de extensiones de archivo a tipos MIME para este volumen. */
  customContentTypes?: Record<string, string>;
  /**
   * Modo de autenticación por volumen. Si no se define, se hereda de
   * `IFilesConfig.auth`; el valor predeterminado es `"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>;
}

/**
 * Manejador del volumen: todos los métodos de VolumeAPI (se ejecutan como el
 * service principal de forma predeterminada) + asUser() para forzar la
 * ejecución por usuario a nivel del SDK.
 */
type VolumeHandle = VolumeAPI & {
  asUser: (req: Request) => VolumeAPI;
};

Resolución del content-type

contentTypeFromPath(filePath, reported?, customTypes?) determina el tipo MIME de un archivo:

  1. Primero consulta el mapa customContentTypes (si está configurado).
  2. Compara la extensión del archivo con el mapa integrado.
  3. Como último recurso, usa el tipo informado por el servidor o application/octet-stream.

Extensiones integradas: .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.

Contexto de usuario

Las rutas HTTP se ejecutan como el service principal o como el usuario final, según el modo de autenticación del volumen:

  • Volúmenes con service principal (opción predeterminada): se usan las credenciales de Databricks del SP para la llamada a la API. La identidad del usuario se extrae del encabezado x-forwarded-user y se pasa a la política de acceso del volumen para autorizarla, pero la llamada del SDK se sigue ejecutando como el SP. Cuando el encabezado no está presente, se le entrega a la política { id: <sp-id>, isServicePrincipal: true } y esta decide si permite la llamada; en la práctica, esa rama solo se activa en desarrollo sin un proxy inverso o cuando un proxy ascendente está mal configurado, ya que los runtimes reales de Databricks Apps siempre reenvían el encabezado. Los grants de UC sobre el SP determinan qué operaciones son posibles.
  • Volúmenes on-behalf-of-user: se usa el token de acceso del usuario final (tomado de x-forwarded-access-token) para crear el cliente del SDK, de modo que la llamada a la API se ejecuta con la identidad del usuario. Tanto la política como el SDK ven al usuario. Los grants de UC sobre el usuario final determinan qué operaciones son posibles. En producción, las solicitudes sin token devuelven 401; en desarrollo (NODE_ENV === "development") recurren al SP con una advertencia.

La API programática devuelve un VolumeHandle que expone directamente todos los métodos de VolumeAPI y un método asUser(req) para forzar la ejecución por usuario. Llamar a un método sin asUser() ejecuta la política y la llamada del SDK como el SP. asUser(req) es una anulación estricta a nivel del SDK: obliga a que cada llamada posterior se ejecute como el usuario final dentro de runInUserContext, sin importar la configuración de auth del volumen. En producción, asUser(req) lanza AuthenticationError.missingToken cuando falta x-forwarded-user o x-forwarded-access-token: ambos encabezados son obligatorios. En desarrollo, en cambio, recurre al service principal, de modo que las pruebas locales sin un proxy inverso siguen funcionando.

Requisitos de recursos

Los recursos de volumen se declaran dinámicamente mediante getResourceRequirements(config) a partir de los volúmenes descubiertos y configurados. Cada clave de volumen genera un recurso obligatorio con el permiso WRITE_VOLUME y una variable de entorno DATABRICKS_VOLUME_{KEY_UPPERCASE}.

Por ejemplo, si están definidos DATABRICKS_VOLUME_UPLOADS y DATABRICKS_VOLUME_EXPORTS, al llamar a files() se generan dos recursos de volumen obligatorios que se validan al arrancar, sin necesidad de configurar volumes de forma explícita.

El manifiesto declara la concesión sobre el service principal. En el caso de los volúmenes OBO (auth: "on-behalf-of-user"), el permiso real debe otorgarse al usuario final: comunícalo por otros medios en la documentación de tu despliegue hasta que el esquema del manifiesto incorpore un campo de ámbito de autenticación por volumen.

Respuestas de error

Todos los errores devuelven JSON:

{
  "error": "Human-readable message",
  "plugin": "files"
}
EstadoDescripción
400Parámetro path ausente o no válido
403La política denegó "{action}" en el volumen "{volumeKey}"
404Clave de volumen desconocida
413La carga supera maxUploadSize
500La operación falló (SDK, red, servicio upstream o error no controlado)

Componentes de frontend

El paquete @databricks/appkit-ui ofrece componentes de React listos para usar que permiten crear un explorador de archivos:

FileBrowser

Un conjunto de componentes combinables para explorar, previsualizar y gestionar archivos en un volumen de 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>
  );
}

Consulta la referencia de componentes de Files (UC) para ver la API completa de props.

Databricks Developer Hub

¿Todo listo para lanzar tu próxima aplicación basada en agentes en minutos?

Leer la documentación