Exportar archivos a Microsoft Azure en Rust
Sube un archivo local de hasta 16 MiB a un contenedor privado existente de Azure Blob Storage con este programa completo en Rust. Envía una sola solicitud condicional y se niega a reemplazar un blob existente. Los archivos vacíos y los datos binarios son entradas válidas.
Este es un ejemplo de mantenimiento para el crate no oficial azure_storage_blobs 0.21.0.
El aviso de Microsoft sobre el crate heredado
indica que no tiene planes de actualizarlo. Para aplicaciones nuevas, evalúa el
SDK oficial azure_storage_blob,
cuyos ejemplos de autenticación usan Microsoft Entra ID. Su API difiere de la de este ejemplo con
versiones fijadas; cambiar únicamente el nombre de la dependencia no constituye una migración.
Requisitos previos
- Un shell de Linux con Bash, Rust, Cargo y el enlazador del sistema que necesita tu instalación de Rust. Este ejemplo se probó con Rust y Cargo 1.98.1, usando la edición 2021. Ese es un entorno probado, no una versión mínima del compilador. Consulta la guía de instalación de Rust para instalar un conjunto de herramientas actualizado.
- Un compilador de C,
pkg-configy las bibliotecas y cabeceras de OpenSSL. El transporte TLS predeterminado los necesita en Linux; la guía de compilación del crate OpenSSL enumera los paquetes para las distribuciones más comunes. Instala estos requisitos antes de crear el proyecto. - Una cuenta de Azure Storage que ya permita la autenticación con Shared Key, junto con su nombre de cuenta y su clave de acceso. Esta opción está destinada a una integración existente con Shared Key. Microsoft recomienda Microsoft Entra ID para las aplicaciones nuevas; no habilites Shared Key solo para seguir este ejemplo.
- Un contenedor privado existente, aprovisionado por separado. El programa no crea contenedores ni cambia su configuración de acceso. Un contenedor privado requiere autorización para leer sus blobs, como se describe en la documentación de Azure sobre el acceso anónimo.
- Un archivo regular completo que permanezca sin cambios durante la subida. Los argumentos del comando, incluida la ruta local, deben ser UTF-8 válido. Ejecuta este ejemplo de forma secuencial.
Configuración del proyecto
Ejecuta este bloque desde un directorio con permisos de escritura. El bloque comprueba las
herramientas de compilación y crea un directorio nuevo; si azure_export ya existe,
se detiene sin entrar en ese proyecto ni modificarlo.
(
command -v cargo >/dev/null &&
command -v rustc >/dev/null &&
command -v cc >/dev/null &&
command -v pkg-config >/dev/null &&
pkg-config --exists openssl &&
mkdir azure_export &&
mkdir azure_export/src
) && cd azure_export
Continúa solo cuando el bloque se haya ejecutado correctamente. Guarda lo siguiente como
Cargo.toml. El [workspace] vacío mantiene este proyecto separado
de un espacio de trabajo de Cargo que lo contenga. Las funciones de sistema de archivos y de E/S
de Tokio se habilitan explícitamente. Conserva el Cargo.lock generado junto con
tu aplicación para mantener las dependencias transitivas resueltas.
Si la creación del directorio falla a mitad del proceso, inspecciona lo que se creó antes de volver a intentarlo. Usa otro directorio padre con permisos de escritura para un nuevo intento; el bloque conserva deliberadamente los archivos existentes.
[package]
name = "azure_export"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
azure_core = "=0.21.0"
azure_storage = "=0.21.0"
azure_storage_blobs = "=0.21.0"
tokio = { version = "=1.48.0", features = ["fs", "io-util", "macros", "rt-multi-thread", "time"] }
Inicialización del cliente de Azure
Proporciona AZURE_STORAGE_ACCOUNT y AZURE_STORAGE_KEY mediante tu gestor de secretos
o el entorno del proceso. La clave es una clave de acceso a la cuenta de almacenamiento, no una
cadena de conexión. El programa llama a StorageCredentials::access_key(); no carga una sesión de
Azure CLI. Mantén la clave fuera del control de versiones y de los argumentos de comandos del
terminal.
Guarda el programa completo que aparece a continuación como src/main.rs.
Implementación de la subida de archivos
El programa lee como máximo 16 MiB más un byte antes de enviar una solicitud y rechaza las entradas
que superen ese tamaño. El límite de 16 MiB es una política de este ejemplo, no el límite de
almacenamiento de Azure. Almacena la entrada en un búfer para una sola operación
put_block_blob; no divide el archivo en bloques preparados ni realiza subidas en
paralelo. El nombre del blob de destino es un argumento independiente de la ruta local.
use azure_core::{prelude::IfMatchCondition, RetryOptions};
use azure_storage::StorageCredentials;
use azure_storage_blobs::prelude::{BlobServiceClient, ClientBuilder};
use std::{env, error::Error, path::Path, time::Duration};
use tokio::{fs::File, io::AsyncReadExt, time::timeout};
type AppResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
const MAX_BYTES: u64 = 16 * 1024 * 1024;
async fn upload_file(
service: &BlobServiceClient,
container: &str,
blob_name: &str,
path: &Path,
) -> AppResult<()> {
if container.is_empty() || blob_name.is_empty() {
return Err("Container and blob name must not be empty".into());
}
let file = File::open(path).await?;
if !file.metadata().await?.is_file() {
return Err("Input must be a regular file".into());
}
let mut contents = Vec::new();
file.take(MAX_BYTES + 1).read_to_end(&mut contents).await?;
if contents.len() as u64 > MAX_BYTES {
return Err("Input exceeds the 16 MiB example limit".into());
}
let blob = service.container_client(container).blob_client(blob_name);
blob.put_block_blob(contents)
.content_type("application/octet-stream")
.if_match(IfMatchCondition::NotMatch("*".to_owned()))
.await?;
Ok(())
}
async fn run() -> AppResult<()> {
let args: Vec<String> = env::args().skip(1).collect();
if args.len() != 3 {
return Err("Usage: azure_export CONTAINER BLOB_NAME FILE".into());
}
let account = env::var("AZURE_STORAGE_ACCOUNT")?;
let key = env::var("AZURE_STORAGE_KEY")?;
let credentials = StorageCredentials::access_key(account.clone(), key);
let service = ClientBuilder::new(account, credentials)
.retry(RetryOptions::none())
.blob_service_client();
timeout(
Duration::from_secs(60),
upload_file(&service, &args[0], &args[1], Path::new(&args[2])),
)
.await??;
println!("Upload confirmed.");
Ok(())
}
#[tokio::main]
async fn main() {
if run().await.is_err() {
eprintln!("Upload was not confirmed; check the input, credentials, container, and destination.");
std::process::exit(1);
}
}
Coloca el archivo que quieras exportar en data.bin dentro del directorio del
proyecto. Con las credenciales proporcionadas y el contenedor privado rust-exports
ya disponible, ejecuta:
cargo run -- rust-exports 'exports/data.bin' './data.bin'
Cargo resuelve las dependencias, compila el programa y lo ejecuta. Los tres argumentos son el
contenedor, el nombre del blob de destino y la ruta del archivo local. Encierra entre comillas las
rutas que contengan espacios. Una solicitud exitosa imprime Upload confirmed. y termina
con código de salida cero. El blob se llama exports/data.bin; la barra forma parte de
su nombre, independientemente de la estructura de directorios local. Elige un nombre de blob sin
barras iniciales ni finales: este SDK elimina esos caracteres de los extremos.
El plazo máximo de 60 segundos abarca la lectura del archivo local y la subida mediante el SDK, después de construir el cliente. La instalación de dependencias y la compilación de Cargo quedan fuera de ese plazo. Incluso un archivo por debajo del límite de tamaño puede superarlo si la conexión es lenta.
Gestión de errores
Un fallo de lectura local, la falta de configuración, una solicitud rechazada o el vencimiento del
plazo producen un código de salida distinto de cero y el diagnóstico genérico
Upload was not confirmed. El programa evita deliberadamente imprimir credenciales o respuestas
sin procesar del SDK. Comprueba primero el archivo local y su tamaño, y luego la cuenta, la clave,
el contenedor y el destino seleccionados. Este diagnóstico por sí solo no identifica el motivo
del rechazo de Azure ni demuestra que no se haya escrito ningún blob.
La opción IfMatchCondition::NotMatch("*") envía If-None-Match: *. El
contrato de cabeceras condicionales de Azure
permite la escritura solo cuando el destino no existe. Si se vuelve a ejecutar el comando para un
destino existente, la operación falla y conserva su contenido anterior. Elige otro nombre de blob
para una nueva exportación en lugar de eliminar un objeto existente para que el comando tenga éxito.
Los reintentos del SDK están deshabilitados. Tras el vencimiento del plazo, una pérdida de conexión o una interrupción, Azure aún puede terminar una subida que ya haya recibido. Detener el proceso local no cancela esa escritura remota. No vuelvas a intentarlo a ciegas ni consideres que una lista vacía demuestra que una escritura pendiente no pueda aparecer más adelante. Inspecciona el destino exacto mediante un cliente autenticado y comprueba el resultado de la primera operación antes de volver a intentarlo; un rechazo posterior puede significar que la primera subida tuvo éxito.
Verifica el resultado almacenado
Upload confirmed. significa que el SDK recibió una respuesta de subida exitosa. El
programa no descarga el blob ni compara los bytes almacenados. Si necesitas verificar una
exportación, descarga ese blob exacto mediante un cliente autenticado y compara todo el archivo
descargado con el archivo de origen sin modificar, incluso si el origen tiene cero bytes.
Comprobar solo el nombre o la cantidad de bytes no permite detectar contenido diferente del mismo
tamaño. Conserva el archivo de origen local hasta completar la verificación.
La privacidad del contenedor es un requisito previo independiente: una subida exitosa no establece su política de acceso. Este ejemplo no ofrece subidas reanudables ni un supervisor de transferencias para producción. Para archivos más grandes o autenticación basada en identidades, comienza con los ejemplos de subida y credenciales del SDK oficial en lugar de ampliar el uso de las versiones heredadas fijadas sin revisar la migración.
