Exporta archivos a Microsoft Azure en Rust
Este ejemplo sube archivos de hasta 16 MiB a un contenedor privado existente de Azure Blob Storage.
Utiliza la API no oficial de azure_storage_blobs, fijada en la versión 0.21.0. El
aviso de Microsoft sobre este crate heredado indica
que no recibirá actualizaciones; su API es distinta de la del SDK más reciente. Considera este
un ejemplo de mantenimiento para ese crate y evalúa el
SDK actual de Azure para aplicaciones nuevas.
Requisitos previos
- Rust y Cargo; el ejemplo se compiló con Rust 1.98.1 (edición 2021).
- Una cuenta de Azure Storage que permita la autenticación con Shared Key.
- Un contenedor privado existente, creado por tu proceso de despliegue.
- Un archivo local completo que no se modificará durante la subida.
Configuración del proyecto
Crea el proyecto:
cargo new azure_export --edition 2021 --vcs none
cd azure_export
Reemplaza Cargo.toml con lo siguiente. Las funcionalidades de sistema de archivos y E/S de Tokio se especifican explícitamente. Incluye el Cargo.lock generado en el control de versiones de tu aplicación para conservar las versiones de las dependencias transitivas.
[package]
name = "azure_export"
version = "0.1.0"
edition = "2021"
[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
Configura 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. Este ejemplo utiliza StorageCredentials::access_key(); no carga las credenciales de Azure CLI.
Guarda el programa completo que aparece a continuación en src/main.rs.
Implementación de la subida de archivos
El archivo se lee con un límite de 16 MiB antes de enviar cualquier solicitud. Se trata de una única
solicitud put_block_blob con los datos en un búfer, no de una subida dividida
automáticamente en fragmentos ni realizada en paralelo. El nombre del blob de destino es un
argumento independiente, para evitar que los nombres de los directorios locales se utilicen
accidentalmente como nombres de blobs.
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);
}
}
Con las credenciales proporcionadas al proceso y el contenedor rust-exports ya aprovisionado, ejecuta:
printf 'Example export\n' > data.txt
cargo run -- rust-exports data.txt data.txt
Manejo de errores
Los errores del sistema de archivos, las credenciales, el SDK y los tiempos de espera se propagan mediante un único tipo de resultado de la aplicación. La CLI devuelve un código de salida distinto de cero sin imprimir credenciales ni respuestas sin procesar del servidor. Los reintentos están deshabilitados explícitamente para que un resultado ambiguo sea visible.
La solicitud condicional rechaza la sobrescritura de un blob existente. Si se agota el tiempo de espera o se pierde la respuesta, es posible que el servidor haya aceptado la subida de todos modos. Inspecciona el destino antes de reintentar; un reintento puede fallar porque la primera solicitud ya creó el blob. Un tiempo de espera agotado detiene la espera local, pero no una escritura remota que Azure ya haya recibido.
Buenas prácticas
Usa un contenedor privado dedicado y restringe quién puede leer la clave de la cuenta. No la incluyas en el control de versiones. Shared Key es el método de autenticación que se muestra aquí; para cambiar a credenciales de identidad, debes configurar expresamente un proveedor de credenciales y los permisos correspondientes.
Mantén los archivos locales de entrada sin cambios hasta que finalice la operación. Para archivos más grandes, implementa la carga de bloques por etapas y una confirmación final de la lista de bloques mediante las API de la versión fijada del crate, o elige un SDK que siga recibiendo mantenimiento y tenga una API adecuada para subidas en streaming.
Consideraciones para producción
Aprovisiona el contenedor por separado con el acceso público deshabilitado. Este programa no crea contenedores ni cambia su política de acceso. Un contenedor inexistente o la falta de permisos suficientes constituyen un fallo de subida, no un motivo para crear infraestructura de forma silenciosa.
La escritura condicional y el límite fijo de memoria hacen que el comportamiento del ejemplo sea predecible, pero no proporcionan subidas reanudables. Registra las subidas confirmadas en tu aplicación y supervisa los fallos sin registrar secretos.
Para un flujo de trabajo gestionado, el Robot de exportación a Azure de Transloadit puede exportar archivos procesados a Azure Blob Storage.
