diff --git a/.github/workflows/e2e-s3-tests.yml b/.github/workflows/e2e-s3-tests.yml new file mode 100644 index 0000000000..5ae62755cf --- /dev/null +++ b/.github/workflows/e2e-s3-tests.yml @@ -0,0 +1,87 @@ +on: + push: + branches: [main] + pull_request: + paths: + - crates/rattler-bin/** + - crates/rattler_index/** + - crates/rattler_upload/** + - crates/rattler_networking/** + - .github/workflows/e2e-s3-tests.yml + +name: E2E S3 Tests + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + RUST_LOG: info + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + DEFAULT_FEATURES: s3 + +jobs: + e2e-minio-test: + name: E2E Upload/Index/Download [Minio] + runs-on: ubuntu-latest + + env: + # Enable sccache. + # + # This environment variable is picked up by pixi build which will then + # set up the rust build using sccache. + SCCACHE_GHA_ENABLED: "true" + + steps: + - name: Checkout source code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + submodules: recursive + + - uses: prefix-dev/setup-pixi@fef5c9568ca6c4ff7707bf840ab0692ba3f08293 # v0.9.0 + with: + environments: minio + + - run: pixi run e2e-s3-minio + + # TODO: add cloudflare R2 integration tests here as well + e2e-aws-s3-test: + name: E2E Upload/Index/Download [AWS S3] + runs-on: ubuntu-latest + # Only run on main branch to avoid creating too many test buckets + # if: github.ref == 'refs/heads/main' + permissions: + id-token: write + contents: read + + env: + # Enable sccache. + # + # This environment variable is picked up by pixi build which will then + # set up the rust build using sccache. + SCCACHE_GHA_ENABLED: "true" + + # The region to create buckets it. + AWS_REGION: eu-west-1 + + # The name of the test bucket to create. + BUCKET: tmp-${{ github.repository_owner }}-${{ github.event.repository.name }}-${{ github.run_id }}-${{ github.run_attempt }} + + steps: + - name: Checkout source code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + submodules: recursive + + - name: Configure AWS (OIDC) + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: arn:aws:iam::239378270001:role/conda-rattler-e2e-test + + - uses: prefix-dev/setup-pixi@fef5c9568ca6c4ff7707bf840ab0692ba3f08293 # v0.9.0 + with: + environments: s3 + + - run: pixi run e2e-s3-aws diff --git a/.gitignore b/.gitignore index 2dc98f6528..52b4f17068 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ test-data/channels/conda-forge/ # Visual studio files .vs/ + +# Local environment files +*.local.* diff --git a/Cargo.lock b/Cargo.lock index bacd668ebb..40ba277c0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + [[package]] name = "async-process" version = "2.4.0" @@ -4497,6 +4503,7 @@ dependencies = [ "console 0.16.0", "indicatif", "itertools 0.14.0", + "miette", "once_cell", "rattler", "rattler_cache", @@ -4505,6 +4512,7 @@ dependencies = [ "rattler_networking", "rattler_repodata_gateway", "rattler_solve", + "rattler_upload", "rattler_virtual_packages", "reqwest", "reqwest-middleware", @@ -4657,6 +4665,7 @@ dependencies = [ "rattler_digest", "rattler_networking", "rattler_package_streaming", + "rattler_s3", "reqwest", "rmp-serde", "serde", @@ -4755,6 +4764,7 @@ name = "rattler_networking" version = "0.25.11" dependencies = [ "anyhow", + "async-once-cell", "async-trait", "aws-config", "aws-sdk-s3", @@ -4906,6 +4916,21 @@ dependencies = [ "zstd", ] +[[package]] +name = "rattler_s3" +version = "0.1.0" +dependencies = [ + "aws-config", + "aws-credential-types", + "aws-sdk-s3", + "clap", + "rattler_networking", + "serde", + "thiserror 2.0.16", + "tracing", + "url", +] + [[package]] name = "rattler_sandbox" version = "0.1.10" @@ -4982,6 +5007,7 @@ dependencies = [ "rattler_networking", "rattler_package_streaming", "rattler_redaction", + "rattler_s3", "rattler_solve", "reqwest", "reqwest-middleware", diff --git a/Cargo.toml b/Cargo.toml index 9b929bd5ea..62dc80876d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ async-compression = { version = "0.4", features = [ ] } async-fd-lock = "0.2.0" fs4 = "0.13.1" +async-once-cell = "0.5.4" async-trait = "0.1.88" axum = { version = "0.8.4", default-features = false, features = [ "tokio", @@ -51,7 +52,7 @@ chrono = { version = "0.4.41", default-features = false, features = [ "serde", "alloc", ] } -clap = { version = "4.5.38", features = ["derive"] } +clap = { version = "4.5.38", features = ["derive", "color", "suggestions", "error-context"] } clap-verbosity-flag = "3.0.3" cmake = "0.1.54" console = { version = "0.16.0", features = ["windows-console-colors"] } @@ -72,17 +73,9 @@ generic-array = "0.14.7" getrandom = { version = "0.3.3", default-features = false } glob = "0.3.2" google-cloud-auth = { version = "0.22.0", default-features = false } -aws-config = { version = "=1.5.18", default-features = false, features = [ - "rt-tokio", - "rustls", - "sso", - "credentials-process", -] } -aws-sdk-s3 = { version = "1.85.0", default-features = false, features = [ - "rt-tokio", - "rustls", - "sigv4a", -] } +aws-config = { version = "=1.5.18", default-features = false } +aws-sdk-s3 = { version = "1.85.0", default-features = false } +aws-credential-types = { version = "1.2.5", default-features = false } hex = "0.4.3" hex-literal = "1.0.0" http = "1.3" @@ -197,13 +190,15 @@ rattler_lock = { path = "crates/rattler_lock", version = "=0.24.0", default-feat rattler_macros = { path = "crates/rattler_macros", version = "=1.0.11", default-features = false } rattler_menuinst = { path = "crates/rattler_menuinst", version = "=0.2.24", default-features = false } rattler_networking = { path = "crates/rattler_networking", version = "=0.25.11", default-features = false } +rattler_package_streaming = { path = "crates/rattler_package_streaming", version = "=0.23.2", default-features = false } rattler_pty = { path = "crates/rattler_pty", version = "=0.2.6", default-features = false } rattler_redaction = { path = "crates/rattler_redaction", version = "=0.1.12", default-features = false } -rattler_package_streaming = { path = "crates/rattler_package_streaming", version = "=0.23.2", default-features = false } rattler_repodata_gateway = { path = "crates/rattler_repodata_gateway", version = "=0.24.2", default-features = false } +rattler_s3 = { path = "crates/rattler_s3", version = "=0.1.0", default-features = false } rattler_sandbox = { path = "crates/rattler_sandbox", version = "=0.1.10", default-features = false } rattler_shell = { path = "crates/rattler_shell", version = "=0.24.10", default-features = false } rattler_solve = { path = "crates/rattler_solve", version = "=3.0.2", default-features = false } +rattler_upload = { path = "crates/rattler_upload", version = "=0.2.1", default-features = false } rattler_virtual_packages = { path = "crates/rattler_virtual_packages", version = "=2.1.4", default-features = false } # This is also a rattler crate, but we only pin it to minor version diff --git a/crates/rattler-bin/Cargo.toml b/crates/rattler-bin/Cargo.toml index 3be8aa78e2..57a15fa72c 100644 --- a/crates/rattler-bin/Cargo.toml +++ b/crates/rattler-bin/Cargo.toml @@ -26,6 +26,7 @@ anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } console = { workspace = true, features = ["windows-console-colors"] } indicatif = { workspace = true } +miette = { workspace = true } once_cell = { workspace = true } rattler = { workspace = true, features = ["indicatif", "cli-tools"] } rattler_conda_types = { workspace = true, default-features = false } @@ -35,6 +36,7 @@ rattler_solve = { workspace = true, default-features = false, features = ["resol rattler_virtual_packages = { workspace = true, default-features = false } rattler_cache = { workspace = true, default-features = false } rattler_menuinst = { workspace = true, default-features = false } +rattler_upload = { workspace = true, features = ["s3"]} reqwest = { workspace = true, features = ["stream"] } reqwest-middleware = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/rattler-bin/pixi.toml b/crates/rattler-bin/pixi.toml new file mode 100644 index 0000000000..aadfe368a1 --- /dev/null +++ b/crates/rattler-bin/pixi.toml @@ -0,0 +1,10 @@ +[package] +name = "rattler" + +[package.build.backend] +name = "pixi-build-rust" +version = "0.4.*" +channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge" +] diff --git a/crates/rattler-bin/src/commands/auth.rs b/crates/rattler-bin/src/commands/auth.rs index cc62779d81..14a64077f1 100644 --- a/crates/rattler-bin/src/commands/auth.rs +++ b/crates/rattler-bin/src/commands/auth.rs @@ -1,8 +1,8 @@ -use anyhow::Result; +use miette::IntoDiagnostic; use rattler::cli::auth; pub type Opt = auth::Args; -pub async fn auth(opt: Opt) -> Result<()> { - auth::execute(opt).await.map_err(|e| anyhow::anyhow!(e)) +pub async fn auth(opt: Opt) -> miette::Result<()> { + auth::execute(opt).await.into_diagnostic() } diff --git a/crates/rattler-bin/src/commands/create.rs b/crates/rattler-bin/src/commands/create.rs index 236b1aa6b5..fb297477bb 100644 --- a/crates/rattler-bin/src/commands/create.rs +++ b/crates/rattler-bin/src/commands/create.rs @@ -9,10 +9,10 @@ use std::{ time::{Duration, Instant}, }; -use anyhow::Context; use clap::ValueEnum; use indicatif::{ProgressBar, ProgressStyle}; use itertools::Itertools; +use miette::{Context, IntoDiagnostic}; use rattler::{ default_cache_dir, install::{IndicatifReporter, Installer, Transaction, TransactionOperation}, @@ -28,7 +28,7 @@ use rattler_solve::{ libsolv_c::{self}, resolvo, SolverImpl, SolverTask, }; -use reqwest::{Client, Url}; +use reqwest::Client; use crate::global_multi_progress; @@ -98,20 +98,21 @@ impl From for rattler_solve::SolveStrategy { } } -pub async fn create(opt: Opt) -> anyhow::Result<()> { - let channel_config = ChannelConfig::default_with_root_dir(env::current_dir()?); - let current_dir = env::current_dir()?; +pub async fn create(opt: Opt) -> miette::Result<()> { + let channel_config = + ChannelConfig::default_with_root_dir(env::current_dir().into_diagnostic()?); + let current_dir = env::current_dir().into_diagnostic()?; let target_prefix = opt .target_prefix .unwrap_or_else(|| current_dir.join(".prefix")); // Make the target prefix absolute - let target_prefix = std::path::absolute(target_prefix)?; + let target_prefix = std::path::absolute(target_prefix).into_diagnostic()?; println!("Target prefix: {}", target_prefix.display()); // Determine the platform we're going to install for let install_platform = if let Some(platform) = opt.platform { - Platform::from_str(&platform)? + Platform::from_str(&platform).into_diagnostic()? } else { Platform::current() }; @@ -125,12 +126,14 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .specs .iter() .map(|spec| MatchSpec::from_str(spec, ParseStrictness::Strict)) - .collect::, _>>()?; + .collect::, _>>() + .into_diagnostic()?; // Find the default cache directory. Create it if it doesnt exist yet. - let cache_dir = default_cache_dir()?; + let cache_dir = default_cache_dir() + .map_err(|e| miette::miette!("could not determine default cache directory: {}", e))?; std::fs::create_dir_all(&cache_dir) - .map_err(|e| anyhow::anyhow!("could not create cache directory: {}", e))?; + .map_err(|e| miette::miette!("could not create cache directory: {}", e))?; // Determine the channels to use from the command line or select the default. // Like matchspecs this also requires the use of the `channel_config` so we @@ -140,10 +143,12 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .unwrap_or_else(|| vec![String::from("conda-forge")]) .into_iter() .map(|channel_str| Channel::from_str(channel_str, &channel_config)) - .collect::, _>>()?; + .collect::, _>>() + .into_diagnostic()?; // Determine the packages that are currently installed in the environment. - let installed_packages = PrefixRecord::collect_from_prefix::(&target_prefix)?; + let installed_packages = + PrefixRecord::collect_from_prefix::(&target_prefix).into_diagnostic()?; // For each channel/subdirectory combination, download and cache the // `repodata.json` that should be available from the corresponding Url. The @@ -155,11 +160,13 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .expect("failed to create client"); let download_client = reqwest_middleware::ClientBuilder::new(download_client) - .with_arc(Arc::new(AuthenticationMiddleware::from_env_and_defaults()?)) + .with_arc(Arc::new( + AuthenticationMiddleware::from_env_and_defaults().into_diagnostic()?, + )) .with(rattler_networking::OciMiddleware) .with(rattler_networking::S3Middleware::new( HashMap::new(), - AuthenticationStorage::from_env_and_defaults()?, + AuthenticationStorage::from_env_and_defaults().into_diagnostic()?, )) .with(rattler_networking::GCSMiddleware) .build(); @@ -173,16 +180,11 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { )) .with_client(download_client.clone()) .with_channel_config(rattler_repodata_gateway::ChannelConfig { - default: SourceConfig::default(), - per_channel: [( - Url::parse("https://prefix.dev")?, - SourceConfig { - sharded_enabled: true, - ..SourceConfig::default() - }, - )] - .into_iter() - .collect(), + default: SourceConfig { + sharded_enabled: true, + ..SourceConfig::default() + }, + per_channel: HashMap::new(), }) .finish(); @@ -198,6 +200,7 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .recursive(true), ) .await + .into_diagnostic() .context("failed to load repodata")?; // Determine the number of records @@ -218,7 +221,7 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .map(|virt_pkg| { let elems = virt_pkg.split('=').collect::>(); Ok(GenericVirtualPackage { - name: elems[0].try_into()?, + name: elems[0].try_into().into_diagnostic()?, version: elems .get(1) .map_or(Version::from_str("0"), |s| Version::from_str(s)) @@ -226,7 +229,7 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { build_string: (*elems.get(2).unwrap_or(&"")).to_string(), }) }) - .collect::>>()?) + .collect::>>()?) } else { rattler_virtual_packages::VirtualPackage::detect( &rattler_virtual_packages::VirtualPackageOverrides::default(), @@ -237,7 +240,7 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .map(|vpkg| GenericVirtualPackage::from(vpkg.clone())) .collect::>() }) - .map_err(anyhow::Error::from) + .into_diagnostic() } })?; @@ -269,11 +272,11 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { // Next, use a solver to solve this specific problem. This provides us with all // the operations we need to apply to our environment to bring it up to // date. - let solver_result = - wrap_in_progress("solving", move || match opt.solver.unwrap_or_default() { - Solver::Resolvo => resolvo::Solver.solve(solver_task), - Solver::LibSolv => libsolv_c::Solver.solve(solver_task), - })?; + let solver_result = wrap_in_progress("solving", move || match opt.solver.unwrap_or_default() { + Solver::Resolvo => resolvo::Solver.solve(solver_task), + Solver::LibSolv => libsolv_c::Solver.solve(solver_task), + }) + .into_diagnostic()?; let mut required_packages: Vec = solver_result.records; @@ -291,7 +294,8 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { None, None, // ignored packages install_platform, - )?; + ) + .into_diagnostic()?; if transaction.operations.is_empty() { println!("No operations necessary"); @@ -315,7 +319,8 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> { .finish(), ) .install(&target_prefix, required_packages) - .await?; + .await + .into_diagnostic()?; if result.transaction.operations.is_empty() { println!( diff --git a/crates/rattler-bin/src/commands/menu.rs b/crates/rattler-bin/src/commands/menu.rs index cdb2a15858..3a334dcb95 100644 --- a/crates/rattler-bin/src/commands/menu.rs +++ b/crates/rattler-bin/src/commands/menu.rs @@ -1,7 +1,8 @@ -use anyhow::{Context, Result}; +use std::{fs, path::PathBuf}; + use clap::Parser; +use miette::IntoDiagnostic; use rattler_conda_types::{menuinst::MenuMode, PackageName, Platform, PrefixRecord}; -use std::{fs, path::PathBuf}; #[derive(Debug, Parser)] pub struct InstallOpt { @@ -13,47 +14,50 @@ pub struct InstallOpt { package_name: PackageName, } -pub async fn install_menu(opts: InstallOpt) -> Result<()> { +pub async fn install_menu(opts: InstallOpt) -> miette::Result<()> { // Find the prefix record in the target_prefix and call `install_menu` on it - let records: Vec = PrefixRecord::collect_from_prefix(&opts.target_prefix)?; + let records: Vec = + PrefixRecord::collect_from_prefix(&opts.target_prefix).into_diagnostic()?; let record = records .iter() .find(|r| r.repodata_record.package_record.name == opts.package_name) - .with_context(|| { - format!( + .ok_or_else(|| { + miette::miette!( "Package {} not found in prefix {:?}", opts.package_name.as_normalized(), opts.target_prefix ) })?; - let prefix = fs::canonicalize(&opts.target_prefix)?; + let prefix = fs::canonicalize(&opts.target_prefix).into_diagnostic()?; rattler_menuinst::install_menuitems_for_record( &prefix, record, Platform::current(), MenuMode::User, - )?; + ) + .into_diagnostic()?; Ok(()) } -pub async fn remove_menu(opts: InstallOpt) -> Result<()> { +pub async fn remove_menu(opts: InstallOpt) -> miette::Result<()> { // Find the prefix record in the target_prefix and call `remove_menu` on it - let records: Vec = PrefixRecord::collect_from_prefix(&opts.target_prefix)?; + let records: Vec = + PrefixRecord::collect_from_prefix(&opts.target_prefix).into_diagnostic()?; let record = records .iter() .find(|r| r.repodata_record.package_record.name == opts.package_name) - .with_context(|| { - format!( + .ok_or_else(|| { + miette::miette!( "Package {} not found in prefix {:?}", opts.package_name.as_normalized(), opts.target_prefix ) })?; - rattler_menuinst::remove_menu_items(&record.installed_system_menus)?; + rattler_menuinst::remove_menu_items(&record.installed_system_menus).into_diagnostic()?; Ok(()) } diff --git a/crates/rattler-bin/src/commands/virtual_packages.rs b/crates/rattler-bin/src/commands/virtual_packages.rs index 92768b5a7a..8f1f81bceb 100644 --- a/crates/rattler-bin/src/commands/virtual_packages.rs +++ b/crates/rattler-bin/src/commands/virtual_packages.rs @@ -1,12 +1,14 @@ +use miette::IntoDiagnostic; use rattler_conda_types::GenericVirtualPackage; use rattler_virtual_packages::VirtualPackageOverrides; #[derive(Debug, clap::Parser)] pub struct Opt {} -pub fn virtual_packages(_opt: Opt) -> anyhow::Result<()> { +pub fn virtual_packages(_opt: Opt) -> miette::Result<()> { let virtual_packages = - rattler_virtual_packages::VirtualPackage::detect(&VirtualPackageOverrides::default())?; + rattler_virtual_packages::VirtualPackage::detect(&VirtualPackageOverrides::default()) + .into_diagnostic()?; for package in virtual_packages { println!("{}", GenericVirtualPackage::from(package.clone())); } diff --git a/crates/rattler-bin/src/main.rs b/crates/rattler-bin/src/main.rs index 72cedd7326..f0df3d0bab 100644 --- a/crates/rattler-bin/src/main.rs +++ b/crates/rattler-bin/src/main.rs @@ -1,17 +1,20 @@ -use crate::writer::IndicatifWriter; use clap::Parser; use indicatif::{MultiProgress, ProgressDrawTarget}; +use miette::IntoDiagnostic; use once_cell::sync::Lazy; use tracing_subscriber::{filter::LevelFilter, util::SubscriberInitExt, EnvFilter}; +use crate::writer::IndicatifWriter; + mod commands; mod writer; /// Returns a global instance of [`indicatif::MultiProgress`]. /// -/// Although you can always create an instance yourself any logging will interrupt pending -/// progressbars. To fix this issue, logging has been configured in such a way to it will not -/// interfere if you use the [`indicatif::MultiProgress`] returning by this function. +/// Although you can always create an instance yourself any logging will +/// interrupt pending progressbars. To fix this issue, logging has been +/// configured in such a way to it will not interfere if you use the +/// [`indicatif::MultiProgress`] returning by this function. pub fn global_multi_progress() -> MultiProgress { static GLOBAL_MP: Lazy = Lazy::new(|| { let mp = MultiProgress::new(); @@ -42,16 +45,17 @@ enum Command { VirtualPackages(commands::virtual_packages::Opt), InstallMenu(commands::menu::InstallOpt), RemoveMenu(commands::menu::InstallOpt), + Upload(Box), } /// Entry point of the `rattler` cli. #[tokio::main] -async fn main() -> anyhow::Result<()> { +async fn main() -> miette::Result<()> { // Parse the command line arguments let opt = Opt::parse(); - // Determine the logging level based on the the verbose flag and the RUST_LOG environment - // variable. + // Determine the logging level based on the the verbose flag and the RUST_LOG + // environment variable. let default_filter = if opt.verbose { LevelFilter::DEBUG } else { @@ -59,9 +63,10 @@ async fn main() -> anyhow::Result<()> { }; let env_filter = EnvFilter::builder() .with_default_directive(default_filter.into()) - .from_env()? + .from_env() + .into_diagnostic()? // filter logs from apple codesign because they are very noisy - .add_directive("apple_codesign=off".parse()?); + .add_directive("apple_codesign=off".parse().into_diagnostic()?); // Setup the tracing subscriber tracing_subscriber::fmt() @@ -69,7 +74,8 @@ async fn main() -> anyhow::Result<()> { .with_writer(IndicatifWriter::new(global_multi_progress())) .without_time() .finish() - .try_init()?; + .try_init() + .into_diagnostic()?; // Dispatch the selected comment match opt.command { @@ -78,5 +84,6 @@ async fn main() -> anyhow::Result<()> { Command::VirtualPackages(opts) => commands::virtual_packages::virtual_packages(opts), Command::InstallMenu(opts) => commands::menu::install_menu(opts).await, Command::RemoveMenu(opts) => commands::menu::remove_menu(opts).await, + Command::Upload(opts) => rattler_upload::upload_from_args(*opts).await, } } diff --git a/crates/rattler_index/Cargo.toml b/crates/rattler_index/Cargo.toml index d4df590766..0da32fb716 100644 --- a/crates/rattler_index/Cargo.toml +++ b/crates/rattler_index/Cargo.toml @@ -52,6 +52,7 @@ rattler_networking = { workspace = true, default-features = false, features = [ rattler_conda_types = { workspace = true, default-features = false } rattler_digest = { workspace = true, default-features = false } rattler_package_streaming = { workspace = true, default-features = false } +rattler_s3 = { workspace = true, features = ["clap"] } reqwest = { workspace = true, default-features = false, features = [ "http2", "macos-system-configuration", diff --git a/crates/rattler_index/pixi.lock b/crates/rattler_index/pixi.lock new file mode 100644 index 0000000000..fd07ef5c91 --- /dev/null +++ b/crates/rattler_index/pixi.lock @@ -0,0 +1,8 @@ +version: 6 +environments: + default: + channels: + - url: https://prefix.dev/pixi-build-backends/ + - url: https://prefix.dev/conda-forge/ + packages: {} +packages: [] diff --git a/crates/rattler_index/pixi.toml b/crates/rattler_index/pixi.toml new file mode 100644 index 0000000000..578551923c --- /dev/null +++ b/crates/rattler_index/pixi.toml @@ -0,0 +1,10 @@ +[package] +name = "rattler_index" + +[package.build.backend] +name = "pixi-build-rust" +version = "0.4.*" +channels = [ + "https://prefix.dev/pixi-build-backends", + "https://prefix.dev/conda-forge" +] diff --git a/crates/rattler_index/src/lib.rs b/crates/rattler_index/src/lib.rs index af63b68578..6e72eb9010 100644 --- a/crates/rattler_index/src/lib.rs +++ b/crates/rattler_index/src/lib.rs @@ -2,46 +2,46 @@ //! files #![deny(missing_docs)] +use std::{ + collections::{HashMap, HashSet}, + io::{BufRead, BufReader, Cursor, Read, Seek}, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; + use anyhow::{Context, Result}; use bytes::buf::Buf; use fs_err::{self as fs}; use futures::{stream::FuturesUnordered, StreamExt}; use fxhash::FxHashMap; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use opendal::{ + layers::RetryLayer, + services::{FsConfig, S3Config}, + Configurator, ErrorKind, Operator, +}; use rattler_conda_types::{ package::{ArchiveIdentifier, ArchiveType, IndexJson, PackageFile, RunExportsJson}, ChannelInfo, PackageRecord, PatchInstructions, Platform, RepoData, Shard, ShardedRepodata, ShardedSubdirInfo, }; use rattler_digest::Sha256Hash; -use rattler_networking::{Authentication, AuthenticationStorage}; use rattler_package_streaming::{ read, seek::{self, stream_conda_content}, }; +use rattler_s3::ResolvedS3Credentials; use serde::Serialize; use sha2::{Digest, Sha256}; -use std::io::{BufRead, BufReader}; -use std::{ - collections::{HashMap, HashSet}, - io::{Cursor, Read, Seek}, - path::{Path, PathBuf}, - str::FromStr, - sync::Arc, -}; use tokio::sync::Semaphore; use url::Url; -use opendal::{ - layers::RetryLayer, - services::{FsConfig, S3Config}, - Configurator, Operator, -}; - const REPODATA_FROM_PACKAGES: &str = "repodata_from_packages.json"; const REPODATA: &str = "repodata.json"; const REPODATA_SHARDS: &str = "repodata_shards.msgpack.zst"; const ZSTD_REPODATA_COMPRESSION_LEVEL: i32 = 19; +const CACHE_CONTROL_IMMUTABLE: &str = "public, max-age=31536000, immutable"; /// Extract the package record from an `index.json` file. pub fn package_record_from_index_json( @@ -435,7 +435,8 @@ pub async fn write_repodata( let unpatched_repodata_path = format!("{subdir}/{REPODATA_FROM_PACKAGES}"); tracing::info!("Writing unpatched repodata to {unpatched_repodata_path}"); let unpatched_repodata_bytes = serde_json::to_vec(&repodata)?; - op.write(&unpatched_repodata_path, unpatched_repodata_bytes) + op.write_with(&unpatched_repodata_path, unpatched_repodata_bytes) + .content_encoding("application/json") .await?; } @@ -460,7 +461,9 @@ pub async fn write_repodata( let repodata_path = format!("{subdir}/{REPODATA}"); tracing::info!("Writing repodata to {repodata_path}"); - op.write(&repodata_path, repodata_bytes).await?; + op.write_with(&repodata_path, repodata_bytes) + .content_encoding("application/json") + .await?; if write_shards { // See CEP 16 @@ -521,7 +524,19 @@ pub async fn write_repodata( let future = async move || { let shard_path = format!("{subdir}/shards/{digest:x}.msgpack.zst"); tracing::trace!("Writing repodata shard to {shard_path}"); - op.write(&shard_path, encoded_shard).await + match op + .write_with(&shard_path, encoded_shard) + .if_not_exists(true) + .cache_control(CACHE_CONTROL_IMMUTABLE) + .await + { + Err(e) if e.kind() == ErrorKind::ConditionNotMatch => { + tracing::trace!("{shard_path} already exists"); + Ok(()) + } + Ok(_metadata) => Ok(()), + Err(e) => Err(e), + } }; tasks.push(tokio::spawn(future())); } @@ -562,7 +577,8 @@ pub struct IndexFsConfig { pub multi_progress: Option, } -/// Create a new `repodata.json` for all packages in the channel at the given directory. +/// Create a new `repodata.json` for all packages in the channel at the given +/// directory. pub async fn index_fs( IndexFsConfig { channel, @@ -594,21 +610,8 @@ pub async fn index_fs( pub struct IndexS3Config { /// The channel to index. pub channel: Url, - /// The region of the S3 bucket. - pub region: String, - /// The endpoint URL of the S3 bucket. - pub endpoint_url: Url, - /// Whether to force path style for the S3 bucket. - pub force_path_style: bool, - /// The access key ID for the S3 bucket. - /// If not set, the authentication storage will be queried. - pub access_key_id: Option, - /// The secret access key for the S3 bucket. - /// If not set, the authentication storage will be queried. - pub secret_access_key: Option, - /// The session token for the S3 bucket. - /// If not set, the authentication storage will be queried. - pub session_token: Option, + /// The resolved credentials to use for S3 access. + pub credentials: ResolvedS3Credentials, /// The target platform to index. pub target_platform: Option, /// The path to a repodata patch to apply to the index. @@ -625,16 +628,12 @@ pub struct IndexS3Config { pub multi_progress: Option, } -/// Create a new `repodata.json` for all packages in the channel at the given S3 URL. +/// Create a new `repodata.json` for all packages in the channel at the given S3 +/// URL. pub async fn index_s3( IndexS3Config { channel, - region, - endpoint_url, - force_path_style, - access_key_id, - secret_access_key, - session_token, + credentials, target_platform, repodata_patch, write_zst, @@ -644,38 +643,22 @@ pub async fn index_s3( multi_progress, }: IndexS3Config, ) -> anyhow::Result<()> { + // Create the S3 configuration for opendal. let mut s3_config = S3Config::default(); s3_config.root = Some(channel.path().to_string()); s3_config.bucket = channel .host_str() .ok_or(anyhow::anyhow!("No bucket in S3 URL"))? .to_string(); - s3_config.region = Some(region); - s3_config.endpoint = Some(endpoint_url.to_string()); - s3_config.enable_virtual_host_style = !force_path_style; - // Use credentials from the CLI if they are provided. - if let (Some(access_key_id), Some(secret_access_key)) = (access_key_id, secret_access_key) { - s3_config.secret_access_key = Some(secret_access_key); - s3_config.access_key_id = Some(access_key_id); - s3_config.session_token = session_token; - } else { - // If they're not provided, check rattler authentication storage for credentials. - let auth_storage = AuthenticationStorage::from_env_and_defaults()?; - let auth = auth_storage.get_by_url(channel)?; - if let ( - _, - Some(Authentication::S3Credentials { - access_key_id, - secret_access_key, - session_token, - }), - ) = auth - { - s3_config.access_key_id = Some(access_key_id); - s3_config.secret_access_key = Some(secret_access_key); - s3_config.session_token = session_token; - } - } + + s3_config.region = Some(credentials.region); + s3_config.endpoint = Some(credentials.endpoint_url.to_string()); + s3_config.secret_access_key = Some(credentials.secret_access_key); + s3_config.access_key_id = Some(credentials.access_key_id); + s3_config.session_token = credentials.session_token; + s3_config.enable_virtual_host_style = + credentials.addressing_style == rattler_s3::S3AddressingStyle::VirtualHost; + index( target_platform, s3_config, @@ -689,14 +672,14 @@ pub async fn index_s3( .await } -/// Create a new `repodata.json` for all packages in the given configurator's root. -/// If `target_platform` is `Some`, only that specific subdir is indexed. +/// Create a new `repodata.json` for all packages in the given configurator's +/// root. If `target_platform` is `Some`, only that specific subdir is indexed. /// Otherwise indexes all subdirs and creates a `repodata.json` for each. /// /// The process is the following: -/// 1. Get all subdirs and create `noarch` and `target_platform` if they do not exist. -/// 2. Iterate subdirs and index each subdir. -/// Therefore, we need to: +/// 1. Get all subdirs and create `noarch` and `target_platform` if they do not +/// exist. +/// 2. Iterate subdirs and index each subdir. Therefore, we need to: /// 1. Collect all uploaded packages in subdir /// 2. Collect all registered packages from `repodata.json` (if exists) /// 3. Determine which packages to add to and to delete from `repodata.json` diff --git a/crates/rattler_index/src/main.rs b/crates/rattler_index/src/main.rs index bbf0a14dde..8c17160b80 100644 --- a/crates/rattler_index/src/main.rs +++ b/crates/rattler_index/src/main.rs @@ -6,6 +6,8 @@ use clap_verbosity_flag::Verbosity; use rattler_conda_types::Platform; use rattler_config::config::concurrency::default_max_concurrent_solves; use rattler_index::{index_fs, index_s3, IndexFsConfig, IndexS3Config}; +use rattler_networking::AuthenticationStorage; +use rattler_s3::S3Credentials; use url::Url; fn parse_s3_url(value: &str) -> Result { @@ -38,7 +40,8 @@ struct Cli { write_shards: Option, /// Whether to force the re-indexing of all packages. - /// Note that this will create a new repodata.json instead of updating the existing one. + /// Note that this will create a new repodata.json instead of updating the + /// existing one. #[arg(short, long, default_value = "false", global = true)] force: bool, @@ -52,8 +55,8 @@ struct Cli { #[arg(long, global = true)] target_platform: Option, - /// The name of the conda package (expected to be in the `noarch` subdir) that should be used for repodata patching. - /// For more information, see `https://prefix.dev/blog/repodata_patching`. + /// The name of the conda package (expected to be in the `noarch` subdir) + /// that should be used for repodata patching. For more information, see `https://prefix.dev/blog/repodata_patching`. #[arg(long, global = true)] repodata_patch: Option, @@ -81,33 +84,13 @@ enum Commands { #[arg(value_parser = parse_s3_url)] channel: Url, - /// The endpoint URL of the S3 backend - #[arg(long, env = "S3_ENDPOINT_URL")] - endpoint_url: Option, - - /// The region of the S3 backend - #[arg(long, env = "S3_REGION")] - region: Option, - - /// Whether to use path-style S3 URLs - #[arg(long, env = "S3_FORCE_PATH_STYLE")] - force_path_style: Option, - - /// The access key ID for the S3 bucket. - #[arg(long, env = "S3_ACCESS_KEY_ID", requires_all = ["secret_access_key"])] - access_key_id: Option, - - /// The secret access key for the S3 bucket. - #[arg(long, env = "S3_SECRET_ACCESS_KEY", requires_all = ["access_key_id"])] - secret_access_key: Option, - - /// The session token for the S3 bucket. - #[arg(long, env = "S3_SESSION_TOKEN", requires_all = ["access_key_id", "secret_access_key"])] - session_token: Option, + #[clap(flatten)] + credentials: rattler_s3::clap::S3CredentialsOpts, }, } -/// The configuration type for rattler-index - just extends rattler config and can load the same TOML files as pixi. +/// The configuration type for rattler-index - just extends rattler config and +/// can load the same TOML files as pixi. pub type Config = rattler_config::config::ConfigBase<()>; /// Entry point of the `rattler-index` cli. @@ -148,35 +131,31 @@ async fn main() -> anyhow::Result<()> { } Commands::S3 { channel, - region, - endpoint_url, - force_path_style, - access_key_id, - secret_access_key, - session_token, + mut credentials, } => { let bucket = channel.host().context("Invalid S3 url")?.to_string(); let s3_config = config .as_ref() .and_then(|config| config.s3_options.0.get(&bucket)); - let region = region - .or(s3_config.map(|c| c.region.clone())) - .context("S3 region not provided")?; - let endpoint_url = endpoint_url - .or(s3_config.map(|c| c.endpoint_url.clone())) - .context("S3 endpoint url not provided")?; - let force_path_style = force_path_style - .or(s3_config.map(|c| c.force_path_style)) - .context("S3 force-path-style not provided")?; + + // Fill in missing credentials from config file if not provided on command line + credentials.region = credentials.region.or(s3_config.map(|c| c.region.clone())); + credentials.endpoint_url = credentials + .endpoint_url + .or(s3_config.map(|c| c.endpoint_url.clone())); + + // Resolve the credentials + let credentials = match Option::::from(credentials) { + Some(credentials) => { + let auth_storage = AuthenticationStorage::from_env_and_defaults()?; + credentials.resolve(&channel, &auth_storage).ok_or_else(|| anyhow::anyhow!("Could not find S3 credentials in the authentication storage, and no credentials were provided via the command line."))? + } + None => rattler_s3::ResolvedS3Credentials::from_sdk().await?, + }; index_s3(IndexS3Config { channel, - region, - endpoint_url, - force_path_style, - access_key_id, - secret_access_key, - session_token, + credentials, target_platform: cli.target_platform, repodata_patch: cli.repodata_patch, write_zst: cli.write_zst.unwrap_or(true), diff --git a/crates/rattler_networking/Cargo.toml b/crates/rattler_networking/Cargo.toml index b0d7cc11bc..ea71f5cc59 100644 --- a/crates/rattler_networking/Cargo.toml +++ b/crates/rattler_networking/Cargo.toml @@ -20,13 +20,23 @@ system-integration = ["keyring", "netrc-rs", "dirs"] [dependencies] anyhow = { workspace = true } +async-once-cell = { workspace = true } async-trait = { workspace = true } base64 = { workspace = true } dirs = { workspace = true, optional = true } fs-err = { workspace = true } google-cloud-auth = { workspace = true, optional = true } -aws-config = { workspace = true, optional = true } -aws-sdk-s3 = { workspace = true, optional = true } +aws-config = { workspace = true, optional = true, features = [ + "rt-tokio", + "rustls", + "sso", + "credentials-process", +] } +aws-sdk-s3 = { workspace = true, optional = true, features = [ + "rt-tokio", + "rustls", + "sigv4a", +] } http = { workspace = true } itertools = { workspace = true } keyring = { workspace = true, optional = true, features = [ diff --git a/crates/rattler_networking/src/s3_middleware.rs b/crates/rattler_networking/src/s3_middleware.rs index d8c16c2ded..c8a1cf51e3 100644 --- a/crates/rattler_networking/src/s3_middleware.rs +++ b/crates/rattler_networking/src/s3_middleware.rs @@ -1,10 +1,12 @@ //! Middleware to handle `s3://` URLs to pull artifacts from an S3 bucket -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; -use anyhow::Error; +use anyhow::{Context, Error}; +use async_once_cell::OnceCell; use async_trait::async_trait; -use aws_config::BehaviorVersion; +use aws_config::{meta::region::RegionProviderChain, BehaviorVersion}; use aws_sdk_s3::presigning::PresigningConfig; +use http::Method; use reqwest::{Request, Response}; use reqwest_middleware::{Middleware, Next, Result as MiddlewareResult}; use url::Url; @@ -55,6 +57,7 @@ pub struct S3 { auth_storage: AuthenticationStorage, config: HashMap, expiration: std::time::Duration, + default_client: Arc>, } /// S3 middleware to authenticate requests. @@ -80,15 +83,23 @@ impl S3 { config, auth_storage, expiration: std::time::Duration::from_secs(300), + default_client: Arc::new(OnceCell::new()), } } + /// Create an S3 client. /// /// # Arguments /// - /// * `url` - The S3 URL to obtain authentication information from the authentication storage. - /// Only respected for custom (non-AWS-based) configuration. + /// * `url` - The S3 URL to obtain authentication information from the + /// authentication storage. Only respected for custom (non-AWS-based) + /// configuration. pub async fn create_s3_client(&self, url: Url) -> Result { + let sdk_config = self + .default_client + .get_or_init(aws_config::defaults(BehaviorVersion::latest()).load()) + .await; + let bucket_name = url .host_str() .ok_or_else(|| anyhow::anyhow!("host should be present in S3 URL"))?; @@ -111,20 +122,17 @@ impl S3 { secret_access_key, session_token, }), - ) => { - let sdk_config = aws_config::defaults(BehaviorVersion::latest()).load().await; - aws_sdk_s3::config::Builder::from(&sdk_config) - .endpoint_url(endpoint_url) - .region(aws_sdk_s3::config::Region::new(region)) - .force_path_style(force_path_style) - .credentials_provider(aws_sdk_s3::config::Credentials::new( - access_key_id, - secret_access_key, - session_token, - None, - "pixi", - )) - } + ) => aws_sdk_s3::config::Builder::from(sdk_config) + .endpoint_url(endpoint_url) + .region(aws_sdk_s3::config::Region::new(region)) + .force_path_style(force_path_style) + .credentials_provider(aws_sdk_s3::config::Credentials::new( + access_key_id, + secret_access_key, + session_token, + None, + "rattler", + )), (_, Some(_)) => { return Err(anyhow::anyhow!("unsupported authentication method")); } @@ -135,12 +143,15 @@ impl S3 { let s3_config = config_builder.build(); Ok(aws_sdk_s3::Client::from_conf(s3_config)) } else { - let sdk_config = aws_config::defaults(BehaviorVersion::latest()).load().await; - let mut s3_config_builder = aws_sdk_s3::config::Builder::from(&sdk_config); + let mut s3_config_builder = aws_sdk_s3::config::Builder::from(sdk_config); + + // Set the region from the default provider chain. + s3_config_builder.set_region(RegionProviderChain::default_provider().region().await); + // Infer if we expect path-style addressing from the endpoint URL. if let Some(endpoint_url) = sdk_config.endpoint_url() { - // If the endpoint URL is localhost, we probably have to use path-style addressing. - // xref: https://github.com/awslabs/aws-sdk-rust/issues/1230 + // If the endpoint URL is localhost, we probably have to use path-style + // addressing. xref: https://github.com/awslabs/aws-sdk-rust/issues/1230 if endpoint_url.starts_with("http://localhost") { s3_config_builder = s3_config_builder.force_path_style(true); } @@ -149,15 +160,17 @@ impl S3 { s3_config_builder = s3_config_builder.force_path_style(true); } } - let client = aws_sdk_s3::Client::from_conf(s3_config_builder.build()); - Ok(client) + Ok(aws_sdk_s3::Client::from_conf(s3_config_builder.build())) } } /// Generate a presigned S3 `GetObject` request. - async fn generate_presigned_s3_url(&self, url: Url) -> MiddlewareResult { + async fn generate_presigned_s3_url(&self, url: Url, method: &Method) -> MiddlewareResult { let client = self.create_s3_client(url.clone()).await?; + let presign_config = PresigningConfig::expires_in(self.expiration) + .map_err(reqwest_middleware::Error::middleware)?; + let bucket_name = url .host_str() .ok_or_else(|| anyhow::anyhow!("host should be present in S3 URL"))?; @@ -166,19 +179,32 @@ impl S3 { .strip_prefix("/") .ok_or_else(|| anyhow::anyhow!("invalid s3 url: {}", url))?; - let builder = client.get_object().bucket(bucket_name).key(key); - - Url::parse( - builder - .presigned( - PresigningConfig::expires_in(self.expiration) - .map_err(reqwest_middleware::Error::middleware)?, - ) + let presigned_request = match *method { + Method::HEAD => client + .head_object() + .bucket(bucket_name) + .key(key) + .presigned(presign_config) .await - .map_err(reqwest_middleware::Error::middleware)? - .uri(), - ) - .map_err(reqwest_middleware::Error::middleware) + .context("failed to presign S3 HEAD request")?, + Method::POST => client + .put_object() + .bucket(bucket_name) + .key(key) + .presigned(presign_config) + .await + .context("failed to presign S3 PUT request")?, + Method::GET => client + .get_object() + .bucket(bucket_name) + .key(key) + .presigned(presign_config) + .await + .context("failed to presign S3 GET request")?, + _ => unimplemented!("Only HEAD, POST and GET are supported for S3 requests"), + }; + + Ok(Url::parse(presigned_request.uri()).context("failed to parse presigned S3 URL")?) } } @@ -191,12 +217,15 @@ impl Middleware for S3Middleware { extensions: &mut http::Extensions, next: Next<'_>, ) -> MiddlewareResult { - if req.url().scheme() == "s3" { - let url = req.url().clone(); - let presigned_url = self.s3.generate_presigned_s3_url(url).await?; - *req.url_mut() = presigned_url.clone(); + // Only intercept `s3://` requests. + if req.url().scheme() != "s3" { + return next.run(req, extensions).await; } - next.run(req, extensions).await + + let url = req.url().clone(); + let presigned_url = self.s3.generate_presigned_s3_url(url, req.method()).await?; + *req.url_mut() = presigned_url; + next.run(req.try_clone().unwrap(), extensions).await } } @@ -204,13 +233,13 @@ impl Middleware for S3Middleware { mod tests { use std::sync::Arc; - use crate::authentication_storage::backends::file::FileStorage; - - use super::*; use rstest::{fixture, rstest}; use temp_env::async_with_vars; use tempfile::{tempdir, TempDir}; + use super::*; + use crate::authentication_storage::backends::file::FileStorage; + #[tokio::test] async fn test_presigned_s3_request_endpoint_url() { let s3 = S3::new(HashMap::new(), AuthenticationStorage::empty()); @@ -224,6 +253,7 @@ mod tests { async { s3.generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await .unwrap() @@ -250,6 +280,7 @@ mod tests { async { s3.generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await .unwrap() @@ -298,6 +329,7 @@ region = eu-central-1 async { s3.generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await .unwrap() @@ -322,6 +354,7 @@ region = eu-central-1 async { s3.generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await .unwrap() @@ -366,6 +399,7 @@ region = eu-central-1 let presigned = s3 .generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await .unwrap(); @@ -395,6 +429,7 @@ region = eu-central-1 let result = s3 .generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await; assert!(result.is_err()); @@ -421,6 +456,7 @@ region = eu-central-1 let result = s3 .generate_presigned_s3_url( Url::parse("s3://rattler-s3-testing/channel/noarch/repodata.json").unwrap(), + &Method::GET, ) .await; assert!(result.is_err()); diff --git a/crates/rattler_repodata_gateway/src/fetch/with_cache.rs b/crates/rattler_repodata_gateway/src/fetch/with_cache.rs index c4ee2a7a2a..ab684159a2 100644 --- a/crates/rattler_repodata_gateway/src/fetch/with_cache.rs +++ b/crates/rattler_repodata_gateway/src/fetch/with_cache.rs @@ -432,6 +432,20 @@ pub async fn fetch_repo_data( }); } + // Fail if the status code is not a success + if !response.status().is_success() { + let status = response.status(); + let body = response.text().await.ok(); + return Err(FetchRepoDataError::HttpError( + reqwest_middleware::Error::Middleware(anyhow::format_err!( + "received unexpected status code ({}) when fetching {}.\n\nBody:\n{}", + status, + repo_data_url.redact(), + body.as_deref().unwrap_or(""), + )), + )); + } + // Get cache headers from the response let cache_headers = CacheHeaders::from(&response); diff --git a/crates/rattler_repodata_gateway/src/gateway/sharded_subdir/tokio/index.rs b/crates/rattler_repodata_gateway/src/gateway/sharded_subdir/tokio/index.rs index f2dfb71455..dbcd462977 100644 --- a/crates/rattler_repodata_gateway/src/gateway/sharded_subdir/tokio/index.rs +++ b/crates/rattler_repodata_gateway/src/gateway/sharded_subdir/tokio/index.rs @@ -6,6 +6,7 @@ use fs_err::tokio as tokio_fs; use futures::{future::OptionFuture, TryFutureExt}; use http::{HeaderMap, Method, Uri}; use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy, RequestLike}; +use rattler_redaction::Redact; use reqwest::Response; use reqwest_middleware::ClientWithMiddleware; use serde::{Deserialize, Serialize}; @@ -45,6 +46,19 @@ pub async fn fetch_index( permit: Option>, ) -> Result { let response = response.error_for_status()?; + if !response.status().is_success() { + let mut url = response.url().clone().redact(); + url.set_query(None); + url.set_fragment(None); + let status = response.status(); + let body = response.text().await.ok(); + return Err(GatewayError::ReqwestMiddlewareError(anyhow::format_err!( + "received unexpected status code ({}) when fetching {}.\n\nBody:\n{}", + status, + url, + body.as_deref().unwrap_or("") + ))); + } // Read the bytes of the response let response_url = response.url().clone(); diff --git a/crates/rattler_s3/Cargo.toml b/crates/rattler_s3/Cargo.toml new file mode 100644 index 0000000000..177c7c0d60 --- /dev/null +++ b/crates/rattler_s3/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rattler_s3" +version = "0.1.0" +description = "A crate to streamline interaction with S3 storage for rattler" +categories.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +edition.workspace = true +readme.workspace = true + +[dependencies] +clap = { workspace = true, optional = true } +thiserror = { workspace = true } +tracing = { workspace = true } +url = { workspace = true } + +aws-config = { workspace = true, features = ["sso", "credentials-process", "rt-tokio", "rustls"] } +aws-sdk-s3 = { workspace = true, features = ["rustls", "rt-tokio"] } +aws-credential-types = { workspace = true } + +rattler_networking = { workspace = true } + +serde = { workspace = true, optional = true } diff --git a/crates/rattler_s3/src/clap.rs b/crates/rattler_s3/src/clap.rs new file mode 100644 index 0000000000..3696aee6c4 --- /dev/null +++ b/crates/rattler_s3/src/clap.rs @@ -0,0 +1,88 @@ +use clap::{Parser, ValueEnum}; +use std::fmt::Display; +use url::Url; + +use crate::S3Credentials; + +#[derive(ValueEnum, Clone, Default, Debug, PartialEq)] +#[clap(rename_all = "kebab_case")] +pub enum S3AddressingStyleOpts { + #[default] + VirtualHost, + Path, +} + +impl From for crate::S3AddressingStyle { + fn from(value: S3AddressingStyleOpts) -> Self { + match value { + S3AddressingStyleOpts::VirtualHost => crate::S3AddressingStyle::VirtualHost, + S3AddressingStyleOpts::Path => crate::S3AddressingStyle::Path, + } + } +} + +impl Display for S3AddressingStyleOpts { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + S3AddressingStyleOpts::VirtualHost => write!(f, "virtual-host"), + S3AddressingStyleOpts::Path => write!(f, "path"), + } + } +} + +/// Manually specified S3 credentials, when these are used no credentials are +/// read through the AWS SDK. +/// +/// See [`super::S3Credentials`] for details on how these credentials are used. +#[derive(Clone, Debug, PartialEq, Parser)] +pub struct S3CredentialsOpts { + /// The endpoint URL of the S3 backend + #[arg(long, env = "S3_ENDPOINT_URL", requires_all = ["region"], help_heading = "S3 Credentials")] + pub endpoint_url: Option, + + /// The region of the S3 backend + #[arg(long, env = "S3_REGION", requires_all = ["endpoint_url"], help_heading = "S3 Credentials")] + pub region: Option, + + /// The access key ID for the S3 bucket. + #[arg(long, env = "S3_ACCESS_KEY_ID", requires_all = ["secret_access_key", "endpoint_url", "region"], help_heading = "S3 Credentials")] + pub access_key_id: Option, + + /// The secret access key for the S3 bucket. + #[arg(long, env = "S3_SECRET_ACCESS_KEY", requires_all = ["access_key_id", "endpoint_url", "region"], help_heading = "S3 Credentials")] + pub secret_access_key: Option, + + /// The session token for the S3 bucket. + #[arg(long, env = "S3_SESSION_TOKEN", requires_all = ["access_key_id", "secret_access_key", "endpoint_url", "region"], help_heading = "S3 Credentials")] + pub session_token: Option, + + /// How to address the bucket + #[arg(long, env = "S3_ADDRESSING_STYLE", requires_all = ["region", "endpoint_url"], help_heading = "S3 Credentials", conflicts_with="force_path_style", default_value_t=S3AddressingStyleOpts::default())] + pub addressing_style: S3AddressingStyleOpts, + + /// Whether to use path-style S3 URLs + #[arg(long, env = "S3_FORCE_PATH_STYLE", requires_all = ["region", "endpoint_url"], help_heading = "S3 Credentials", conflicts_with="addressing_style", hide = true, help = "[deprecated] Whether to use path-style S3 URLs")] + pub force_path_style: Option, +} + +impl From for Option { + fn from(mut value: S3CredentialsOpts) -> Self { + if value.force_path_style.is_some() { + tracing::warn!("The `--force-path-style` option is deprecated, please use `--addressing-style=path` instead."); + value.addressing_style = S3AddressingStyleOpts::Path; + } + + if let (Some(endpoint_url), Some(region)) = (value.endpoint_url, value.region) { + Some(S3Credentials { + endpoint_url, + region, + access_key_id: value.access_key_id, + secret_access_key: value.secret_access_key, + session_token: value.session_token, + addressing_style: value.addressing_style.into(), + }) + } else { + None + } + } +} diff --git a/crates/rattler_s3/src/lib.rs b/crates/rattler_s3/src/lib.rs new file mode 100644 index 0000000000..7fa4321a7c --- /dev/null +++ b/crates/rattler_s3/src/lib.rs @@ -0,0 +1,179 @@ +#[cfg(feature = "clap")] +pub mod clap; + +use aws_config::{BehaviorVersion, Region}; +use aws_credential_types::provider::error::CredentialsError; +use aws_sdk_s3::config::{Credentials, ProvideCredentials}; +use rattler_networking::{Authentication, AuthenticationStorage}; +use url::Url; + +/// How to address S3 buckets. +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum S3AddressingStyle { + /// Address the bucket as a virtual host. E.g. . + #[default] + VirtualHost, + + /// Address the bucket using a path. E.g. . + Path, +} + +/// Rattler based crates always either use S3 credentials specified by the user +/// through CLI arguments combined with credentials coming from `rattler auth`, +/// or everything is loaded through the AWS SDK. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct S3Credentials { + /// The endpoint URL of the S3 backend + pub endpoint_url: Url, + + /// The region of the S3 backend + pub region: String, + + /// The addressing style to use for the bucket. + #[cfg_attr(feature = "serde", serde(default))] + pub addressing_style: S3AddressingStyle, + + /// The access key ID for the S3 bucket. + pub access_key_id: Option, + + /// The secret access key for the S3 bucket. + pub secret_access_key: Option, + + /// The session token for the S3 bucket. + pub session_token: Option, +} + +/// The resolved S3 credentials. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ResolvedS3Credentials { + /// The endpoint URL of the S3 backend + pub endpoint_url: Url, + + /// The region of the S3 backend + pub region: String, + + /// How to address the S3 buckets. + pub addressing_style: S3AddressingStyle, + + /// The access key ID for the S3 bucket. + pub access_key_id: String, + + /// The secret access key for the S3 bucket. + pub secret_access_key: String, + + /// The session token for the S3 bucket. + pub session_token: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum FromSDKError { + #[error("No credentials provider found in AWS SDK configuration")] + NoCredentialsProvider, + + #[error("Could not determine region from AWS SDK configuration")] + MissingRegion, + + #[error("Could not determine endpoint from AWS SDK configuration")] + MissingEndpoint, + + #[error("Failed to parse endpoint from AWS SDK configuration")] + InvalidEndpoint(#[source] url::ParseError), + + #[error(transparent)] + CredentialsError(CredentialsError), +} + +impl ResolvedS3Credentials { + pub async fn from_sdk() -> Result { + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let s3_config = aws_sdk_s3::config::Builder::from(&config).build(); + + let region = s3_config + .region() + .map(Region::to_string) + .ok_or(FromSDKError::MissingRegion)?; + let endpoint_url_str = config.endpoint_url().unwrap_or("https://s3.amazonaws.com"); + let endpoint_url = Url::parse(endpoint_url_str).map_err(FromSDKError::InvalidEndpoint)?; + + let Some(credentials_provider) = config.credentials_provider() else { + return Err(FromSDKError::NoCredentialsProvider); + }; + let credentials: Credentials = credentials_provider + .provide_credentials() + .await + .map_err(FromSDKError::CredentialsError)?; + let access_key_id = credentials.access_key_id().to_string(); + let secret_access_key = credentials.secret_access_key().to_string(); + let session_token = credentials.session_token().map(ToString::to_string); + + // Address style is not exposed in the AWS SDK config, so we use the default. + // See: + let addressing_style = S3AddressingStyle::default(); + + Ok(Self { + endpoint_url, + region, + addressing_style, + access_key_id, + secret_access_key, + session_token, + }) + } +} + +impl S3Credentials { + /// Try to resolve the S3 credentials using the provided authentication + /// storage. + pub fn resolve( + self, + bucket_url: &Url, + auth_storage: &AuthenticationStorage, + ) -> Option { + let (access_key_id, secret_access_key, session_token) = + if let (Some(access_key_id), Some(secret_access_key)) = + (self.access_key_id, self.secret_access_key) + { + (access_key_id, secret_access_key, self.session_token) + } else if let Some((access_key_id, secret_access_key, session_token)) = + load_s3_credentials_from_auth_storage(auth_storage, bucket_url.clone()) + { + // Use the credentials from the authentication storage if they are available. + (access_key_id, secret_access_key, session_token) + } else { + return None; + }; + + Some(ResolvedS3Credentials { + endpoint_url: self.endpoint_url, + region: self.region, + access_key_id, + secret_access_key, + session_token, + addressing_style: self.addressing_style, + }) + } +} + +fn load_s3_credentials_from_auth_storage( + auth_storage: &AuthenticationStorage, + channel: Url, +) -> Option<(String, String, Option)> { + let auth = auth_storage.get_by_url(channel).ok()?; + if let ( + _, + Some(Authentication::S3Credentials { + access_key_id, + secret_access_key, + session_token, + }), + ) = auth + { + Some((access_key_id, secret_access_key, session_token)) + } else { + None + } +} diff --git a/crates/rattler_upload/Cargo.toml b/crates/rattler_upload/Cargo.toml index 28f9c4fc16..3ed5accb68 100644 --- a/crates/rattler_upload/Cargo.toml +++ b/crates/rattler_upload/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true readme.workspace = true [features] -s3 = ["rattler_networking/s3"] +s3 = ["rattler_networking/s3", "rattler_s3"] [dependencies] rattler_conda_types = { workspace = true, default-features = false } @@ -21,29 +21,30 @@ rattler_redaction = { workspace = true, default-features = false } rattler_package_streaming = { workspace = true, default-features = false } rattler_config = { workspace = true, default-features = false } rattler_solve = { workspace = true } -miette = { version = "7.6.0", features = ["fancy"] } -clap = { version = "4.5.37", features = ["derive", "env", "cargo"] } +rattler_s3 = { workspace = true, optional = true, features = ["clap"] } +miette = { workspace = true, features = ["fancy"] } +clap = { workspace = true, features = ["env"] } fs-err = { workspace = true, features = ["tokio"] } -futures = "0.3.31" -indicatif = "0.18.0" -opendal = { version = "0.54.0", default-features = false, features = [ +futures = { workspace = true } +indicatif = { workspace = true } +opendal = { workspace = true , default-features = false, features = [ "services-s3", ]} -reqwest-retry = "0.7.0" -tokio-util = { version = "0.7.15", features = ["codec", "compat"] } -reqwest = { version = "0.12.15", default-features = false, features = [ +reqwest-retry = { workspace = true } +tokio-util = { workspace = true, features = ["codec", "compat"] } +reqwest = { workspace = true, default-features = false, features = [ "multipart", ]} -url = "2.5.4" -tracing = "0.1.41" -reqwest-middleware = { version = "0.4.2", features = ["json"] } -serde_yaml = "0.9.34" -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" -base64 = "0.22.1" -tempfile = "3.19.1" -thiserror = "2.0.12" -tokio = { version = "1.44.2", features = [ +url = { workspace = true } +tracing = { workspace = true } +reqwest-middleware = { workspace = true, features = ["json"] } +serde_yaml = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +base64 = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = [ "rt", "macros", "rt-multi-thread", @@ -51,7 +52,7 @@ tokio = { version = "1.44.2", features = [ ] } [target.'cfg(not(target_os = "windows"))'.dependencies] -sha2 = { version = "0.10.8", features = ["asm"] } +sha2 = { workspace = true, features = ["asm"] } [target.'cfg(target_os = "windows")'.dependencies] -sha2 = { version = "0.10.8" } +sha2 = { workspace = true } diff --git a/crates/rattler_upload/src/lib.rs b/crates/rattler_upload/src/lib.rs index 84e4f19f70..41adaf37fd 100644 --- a/crates/rattler_upload/src/lib.rs +++ b/crates/rattler_upload/src/lib.rs @@ -1,13 +1,13 @@ pub mod upload; pub(crate) mod utils; -use crate::utils::tool_configuration; use miette::IntoDiagnostic; use rattler_conda_types::package::ArchiveType; use upload::opt::{ AnacondaData, ArtifactoryData, CondaForgeData, PrefixData, QuetzData, ServerType, UploadOpts, }; +use crate::utils::tool_configuration; /// Upload package to different channels pub async fn upload_from_args(args: UploadOpts) -> miette::Result<()> { // Validate package files are provided @@ -54,13 +54,9 @@ pub async fn upload_from_args(args: UploadOpts) -> miette::Result<()> { upload::upload_package_to_s3( &store, s3_opts.channel, - s3_opts.endpoint_url, - s3_opts.region, - s3_opts.force_path_style, - s3_opts.access_key_id, - s3_opts.secret_access_key, - s3_opts.session_token, + s3_opts.credentials.into(), &args.package_files, + s3_opts.force, ) .await } diff --git a/crates/rattler_upload/src/upload/mod.rs b/crates/rattler_upload/src/upload/mod.rs index 0dc51054f4..965bb13e80 100644 --- a/crates/rattler_upload/src/upload/mod.rs +++ b/crates/rattler_upload/src/upload/mod.rs @@ -26,12 +26,11 @@ pub mod conda_forge; pub mod opt; mod package; mod prefix; -mod trusted_publishing; - #[cfg(feature = "s3")] -use opendal::{services::S3Config, Configurator, Operator}; +mod s3; +mod trusted_publishing; #[cfg(feature = "s3")] -use std::net::Ipv4Addr; +pub use s3::upload_package_to_s3; pub use prefix::upload_package_to_prefix; @@ -270,88 +269,6 @@ pub async fn upload_package_to_anaconda( } Ok(()) } -#[cfg(feature = "s3")] -/// Uploads a package to a channel in an S3 bucket. -#[allow(clippy::too_many_arguments)] -pub async fn upload_package_to_s3( - storage: &AuthenticationStorage, - channel: Url, - endpoint_url: Url, - region: String, - force_path_style: bool, - access_key_id: Option, - secret_access_key: Option, - session_token: Option, - package_files: &Vec, -) -> miette::Result<()> { - let bucket = channel - .host_str() - .ok_or_else(|| miette::miette!("Failed to get host from channel URL"))?; - - if let Some(host_endpoint) = endpoint_url.host_str() { - if host_endpoint.parse::().is_ok() && !force_path_style { - return Err(miette::miette!( - "Endpoint URL {} (IPv4 address) cannot be used without path style, please use --force-path-style", - endpoint_url - )); - } - } - - let mut s3_config = S3Config::default(); - s3_config.root = Some(channel.path().to_string()); - s3_config.bucket = bucket.to_string(); - s3_config.region = Some(region); - s3_config.endpoint = Some(endpoint_url.to_string()); - s3_config.enable_virtual_host_style = !force_path_style; - // Use credentials from the CLI if they are provided. - if let (Some(access_key_id), Some(secret_access_key)) = (access_key_id, secret_access_key) { - s3_config.secret_access_key = Some(secret_access_key); - s3_config.access_key_id = Some(access_key_id); - s3_config.session_token = session_token; - } else { - // If they're not provided, check rattler authentication storage for credentials. - let auth = storage.get_by_url(channel.clone()).into_diagnostic()?; - if let ( - _, - Some(Authentication::S3Credentials { - access_key_id, - secret_access_key, - session_token, - }), - ) = auth - { - s3_config.access_key_id = Some(access_key_id); - s3_config.secret_access_key = Some(secret_access_key); - s3_config.session_token = session_token; - } - } - - let builder = s3_config.into_builder(); - let op = Operator::new(builder).into_diagnostic()?.finish(); - - for package_file in package_files { - let package = ExtractedPackage::from_package_file(package_file)?; - let subdir = package - .subdir() - .ok_or_else(|| miette::miette!("Failed to get subdir"))?; - let filename = package - .filename() - .ok_or_else(|| miette::miette!("Failed to get filename"))?; - let key = format!("{subdir}/{filename}"); - let body = fs::read(package_file).await.into_diagnostic()?; - op.write_with(&key, body) - .if_not_exists(true) - .await - .into_diagnostic()?; - - tracing::info!( - "Uploaded package to s3://{bucket}{}/{key}", - channel.path().to_string() - ); - } - - Ok(()) -} async fn send_request_with_retry( prepared_request: reqwest::RequestBuilder, diff --git a/crates/rattler_upload/src/upload/opt.rs b/crates/rattler_upload/src/upload/opt.rs index 2d849d37b9..e170a579b3 100644 --- a/crates/rattler_upload/src/upload/opt.rs +++ b/crates/rattler_upload/src/upload/opt.rs @@ -1,18 +1,19 @@ //! Command-line options. -use clap::{arg, Parser}; -use rattler_conda_types::utils::url_with_trailing_slash::UrlWithTrailingSlash; -use rattler_conda_types::{NamedChannelOrUrl, Platform}; -use rattler_networking::mirror_middleware; -use rattler_networking::AuthenticationStorage; -use rattler_solve::ChannelPriority; use std::{collections::HashMap, path::PathBuf, str::FromStr}; -use tracing::warn; -use url::Url; +use clap::{arg, Parser}; +use rattler_conda_types::{ + utils::url_with_trailing_slash::UrlWithTrailingSlash, NamedChannelOrUrl, Platform, +}; #[cfg(feature = "s3")] use rattler_networking::s3_middleware; +use rattler_networking::{mirror_middleware, AuthenticationStorage}; +use rattler_solve::ChannelPriority; +use tracing::warn; +use url::Url; -/// The configuration type for rattler-build - just extends rattler / pixi config and can load the same TOML files. +/// The configuration type for rattler-build - just extends rattler / pixi +/// config and can load the same TOML files. pub type Config = rattler_config::config::ConfigBase<()>; /// Container for `rattler_solver::ChannelPriority` so that it can be parsed @@ -98,7 +99,8 @@ impl CommonData { allow_insecure_host: Option>, ) -> Self { // mirror config - // todo: this is a duplicate in pixi and pixi-pack: do it like in `compute_s3_config` + // todo: this is a duplicate in pixi and pixi-pack: do it like in + // `compute_s3_config` let mut mirror_config = HashMap::new(); tracing::debug!("Using mirrors: {:?}", config.mirrors); @@ -407,6 +409,7 @@ pub struct AnacondaOpts { pub force: bool, } +#[cfg(feature = "s3")] fn parse_s3_url(value: &str) -> Result { let url: Url = Url::parse(value).map_err(|err| format!("`{value}` isn't a valid URL: {err}"))?; @@ -420,39 +423,20 @@ fn parse_s3_url(value: &str) -> Result { } /// Options for uploading to S3 +#[cfg(feature = "s3")] #[derive(Clone, Debug, PartialEq, Parser)] pub struct S3Opts { - /// The channel URL in the S3 bucket to upload the package to, e.g., `s3://my-bucket/my-channel` + /// The channel URL in the S3 bucket to upload the package to, e.g., + /// `s3://my-bucket/my-channel` #[arg(short, long, env = "S3_CHANNEL", value_parser = parse_s3_url)] pub channel: Url, - /// The endpoint URL of the S3 backend - #[arg( - long, - env = "S3_ENDPOINT_URL", - default_value = "https://s3.amazonaws.com" - )] - pub endpoint_url: Url, - - /// The region of the S3 backend - #[arg(long, env = "S3_REGION", default_value = "eu-central-1")] - pub region: String, - - /// Whether to use path-style S3 URLs - #[arg(long, env = "S3_FORCE_PATH_STYLE", default_value = "false")] - pub force_path_style: bool, - - /// The access key ID for the S3 bucket. - #[arg(long, env = "S3_ACCESS_KEY_ID", requires_all = ["secret_access_key"])] - pub access_key_id: Option, - - /// The secret access key for the S3 bucket. - #[arg(long, env = "S3_SECRET_ACCESS_KEY", requires_all = ["access_key_id"])] - pub secret_access_key: Option, + #[clap(flatten)] + pub credentials: rattler_s3::clap::S3CredentialsOpts, - /// The session token for the S3 bucket. - #[arg(long, env = "S3_SESSION_TOKEN", requires_all = ["access_key_id", "secret_access_key"])] - pub session_token: Option, + /// Replace files if it already exists. + #[arg(long)] + pub force: bool, } #[derive(Debug)] @@ -623,7 +607,8 @@ pub struct DebugOpts { #[clap(flatten)] pub common: CommonOpts, - /// Name of the specific output to debug (only required when a recipe has multiple outputs) + /// Name of the specific output to debug (only required when a recipe has + /// multiple outputs) #[arg(long, help = "Name of the specific output to debug")] pub output_name: Option, } @@ -650,8 +635,8 @@ pub struct DebugData { } impl DebugData { - /// Generate a new `TestData` struct from `TestOpts` and an optional pixi config. - /// `TestOpts` have higher priority than the pixi config. + /// Generate a new `TestData` struct from `TestOpts` and an optional pixi + /// config. `TestOpts` have higher priority than the pixi config. pub fn from_opts_and_config(opts: DebugOpts, config: Option) -> Self { Self { recipe_path: opts.recipe, diff --git a/crates/rattler_upload/src/upload/s3.rs b/crates/rattler_upload/src/upload/s3.rs new file mode 100644 index 0000000000..b1477fb2cc --- /dev/null +++ b/crates/rattler_upload/src/upload/s3.rs @@ -0,0 +1,83 @@ +use std::path::PathBuf; + +use miette::IntoDiagnostic; +use opendal::{services::S3Config, Configurator, ErrorKind, Operator}; +use rattler_networking::AuthenticationStorage; +use rattler_s3::{ResolvedS3Credentials, S3Credentials}; +use url::Url; + +use crate::upload::package::ExtractedPackage; + +/// Uploads a package to a channel in an S3 bucket. +#[allow(clippy::too_many_arguments)] +pub async fn upload_package_to_s3( + auth_storage: &AuthenticationStorage, + channel: Url, + credentials: Option, + package_files: &Vec, + force: bool, +) -> miette::Result<()> { + let bucket = channel + .host_str() + .ok_or(miette::miette!("No bucket in S3 URL"))?; + + // Create the S3 configuration for opendal. + let mut s3_config = S3Config::default(); + s3_config.root = Some(channel.path().to_string()); + s3_config.bucket = bucket.to_string(); + + // Resolve the credentials to use. + let resolved_credentials = match credentials { + Some(credentials) => credentials + .resolve(&channel, auth_storage) + .ok_or_else(|| miette::miette!("Could not find S3 credentials in the authentication storage, and no credentials were provided via the command line."))?, + None => { + ResolvedS3Credentials::from_sdk().await.into_diagnostic()? + } + }; + + s3_config.endpoint = Some(resolved_credentials.endpoint_url.to_string()); + s3_config.region = Some(resolved_credentials.region); + s3_config.access_key_id = Some(resolved_credentials.access_key_id); + s3_config.secret_access_key = Some(resolved_credentials.secret_access_key); + s3_config.session_token = resolved_credentials.session_token; + s3_config.enable_virtual_host_style = + resolved_credentials.addressing_style == rattler_s3::S3AddressingStyle::VirtualHost; + + let builder = s3_config.into_builder(); + let op = Operator::new(builder).into_diagnostic()?.finish(); + + for package_file in package_files { + let package = ExtractedPackage::from_package_file(package_file)?; + let subdir = package + .subdir() + .ok_or_else(|| miette::miette!("Failed to get subdir"))?; + let filename = package + .filename() + .ok_or_else(|| miette::miette!("Failed to get filename"))?; + let key = format!("{subdir}/{filename}"); + let body = fs_err::tokio::read(package_file).await.into_diagnostic()?; + match op + .write_with(&key, body) + .content_disposition(&format!("attachment; filename={filename}")) + .if_not_exists(!force) + .await + { + Err(e) if e.kind() == ErrorKind::ConditionNotMatch => { + tracing::info!( + "Skipped package s3://{bucket}{}/{key}, the package already exists. Use --force to overwrite.", + channel.path().to_string() + ); + } + Ok(_metadata) => { + tracing::info!( + "Uploaded package to s3://{bucket}{}/{key}", + channel.path().to_string() + ); + } + Err(e) => return Err(e).into_diagnostic(), + } + } + + Ok(()) +} diff --git a/js-rattler/Cargo.lock b/js-rattler/Cargo.lock index 8ee6e9b7e9..2a0723032a 100644 --- a/js-rattler/Cargo.lock +++ b/js-rattler/Cargo.lock @@ -106,6 +106,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + [[package]] name = "async-trait" version = "0.1.88" @@ -1897,6 +1903,7 @@ name = "rattler_networking" version = "0.25.11" dependencies = [ "anyhow", + "async-once-cell", "async-trait", "base64", "fs-err", diff --git a/pixi.lock b/pixi.lock index d2da461ce5..ad953cf1b4 100644 --- a/pixi.lock +++ b/pixi.lock @@ -503,6 +503,274 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_26.conda - conda: https://prefix.dev/conda-forge/win-64/vs2022_win-64-19.44.35207-ha74f236_30.conda - conda: https://prefix.dev/conda-forge/noarch/vswhere-3.1.7-h40126e0_1.conda + minio: + channels: + - url: https://prefix.dev/conda-forge/ + packages: + linux-64: + - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.1.0-h767d61c_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.1.0-h767d61c_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/minio-client-2025.07.21.05.28.08-hfc2019e_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/minio-server-2025.01.20.14.49.07-hbcca054_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/nushell-0.106.1-hb0b18c3_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda + - conda: crates/rattler-bin + subdir: linux-64 + - conda: crates/rattler_index + subdir: linux-64 + osx-64: + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libcxx-21.1.0-h3d58e20_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/minio-client-2025.07.21.05.28.08-hccc6df8_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/minio-server-2025.01.20.14.49.07-h8857fd0_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/nushell-0.106.1-h088d292_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda + - conda: crates/rattler-bin + subdir: osx-64 + - conda: crates/rattler_index + subdir: osx-64 + osx-arm64: + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.0-hf598326_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/minio-client-2025.07.21.05.28.08-h820172f_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/minio-server-2025.01.20.14.49.07-hf0a4a13_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/nushell-0.106.1-h1b39ca6_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda + - conda: crates/rattler-bin + subdir: osx-arm64 + - conda: crates/rattler_index + subdir: osx-arm64 + win-64: + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://prefix.dev/conda-forge/win-64/minio-client-2025.07.21.05.28.08-h11686cb_0.conda + - conda: https://prefix.dev/conda-forge/win-64/minio-server-2025.01.20.14.49.07-h56e8100_1.conda + - conda: https://prefix.dev/conda-forge/win-64/nushell-0.106.1-hdab3696_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda + - conda: crates/rattler-bin + subdir: win-64 + - conda: crates/rattler_index + subdir: win-64 + s3: + channels: + - url: https://prefix.dev/conda-forge/ + packages: + linux-64: + - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-auth-0.9.0-h0fbd49f_19.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-cal-0.9.2-he7b75e1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-common-0.12.4-hb03c661_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-compression-0.3.1-h92c474e_6.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-event-stream-0.5.5-h149bd38_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-http-0.10.4-h37a7233_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-io-0.21.2-h6252d9a_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-mqtt-0.13.3-h19deb91_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-s3-0.8.6-h800fcd2_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h92c474e_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/aws-checksums-0.2.7-h92c474e_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/awscli-2.28.21-py313h78bf25f_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/awscrt-0.27.5-py313ha03020b_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.8.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/docutils-0.18.1-py313h78bf25f_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jmespath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-15.1.0-h767d61c_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libgomp-15.1.0-h767d61c_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_4.conda + - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/nushell-0.106.1-hb0b18c3_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda + - conda: https://prefix.dev/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.5-hec9711d_102_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml-0.17.17-py313h536fd9c_3.conda + - conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml.clib-0.2.8-py313h536fd9c_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/s2n-1.5.23-h8e187f5_0.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-1.26.19-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda + - conda: crates/rattler-bin + subdir: linux-64 + - conda: crates/rattler_index + subdir: linux-64 + osx-64: + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-auth-0.9.0-h9972aa3_19.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-cal-0.9.2-h6f29d6d_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-common-0.12.4-h1c43f85_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-compression-0.3.1-h7a4e982_6.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-event-stream-0.5.5-hf5ae603_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-http-0.10.4-hb3df2dd_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-io-0.21.2-h46f635e_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-mqtt-0.13.3-h6fbeeec_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-s3-0.8.6-he7aa9d9_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-c-sdkutils-0.2.4-h7a4e982_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/aws-checksums-0.2.7-h7a4e982_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/awscli-2.28.21-py312hb401068_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/awscrt-0.27.5-py312hef7181d_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.1.0-py312h462f358_4.conda + - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.8.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/docutils-0.18.1-py312hb401068_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jmespath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libcxx-21.1.0-h3d58e20_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/nushell-0.106.1-h088d292_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda + - conda: https://prefix.dev/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/python-3.12.11-h9ccd52b_0_cpython.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml-0.17.17-py312h3d0f464_3.conda + - conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml.clib-0.2.8-py312h3d0f464_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-1.26.19-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda + - conda: crates/rattler-bin + subdir: osx-64 + - conda: crates/rattler_index + subdir: osx-64 + osx-arm64: + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-auth-0.9.0-h9eee66f_19.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-cal-0.9.2-hd08b81e_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-common-0.12.4-h6caf38d_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-compression-0.3.1-habbe1e8_6.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-event-stream-0.5.5-hd1b68e1_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-http-0.10.4-h09a8a51_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-io-0.21.2-hc6344be_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-mqtt-0.13.3-h625c29d_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-s3-0.8.6-h6ded10d_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-habbe1e8_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/aws-checksums-0.2.7-habbe1e8_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/awscli-2.28.21-py313h8f79df9_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/awscrt-0.27.5-py313h3c677f0_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.8.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/docutils-0.18.1-py313h8f79df9_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda + - conda: https://prefix.dev/conda-forge/noarch/jmespath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.0-hf598326_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/nushell-0.106.1-h1b39ca6_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda + - conda: https://prefix.dev/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.5-hf3f3da0_102_cp313.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml-0.17.17-py313h63a2874_3.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.8-py313h63a2874_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-1.26.19-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda + - conda: crates/rattler-bin + subdir: osx-arm64 + - conda: crates/rattler_index + subdir: osx-arm64 + win-64: + - conda: https://prefix.dev/conda-forge/win-64/aws-c-auth-0.9.0-hd9a66b3_19.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-cal-0.9.2-hef2a5b8_1.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-common-0.12.4-hfd05255_0.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-compression-0.3.1-ha8a2810_6.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-event-stream-0.5.5-hccb7587_3.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-http-0.10.4-h04b3cea_0.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-io-0.21.2-h20b9e97_1.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-mqtt-0.13.3-h6b158f5_3.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-s3-0.8.6-h46905be_2.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-c-sdkutils-0.2.4-ha8a2810_1.conda + - conda: https://prefix.dev/conda-forge/win-64/aws-checksums-0.2.7-ha8a2810_2.conda + - conda: https://prefix.dev/conda-forge/win-64/awscli-2.28.21-py312h2e8e312_0.conda + - conda: https://prefix.dev/conda-forge/win-64/awscrt-0.27.5-py312h44c0e62_0.conda + - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.1.0-py312hbb81ca0_4.conda + - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda + - conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda + - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/distro-1.8.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/docutils-0.18.1-py312h2e8e312_1.conda + - conda: https://prefix.dev/conda-forge/noarch/jmespath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libffi-3.4.6-h537db12_1.conda + - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://prefix.dev/conda-forge/win-64/nushell-0.106.1-hdab3696_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda + - conda: https://prefix.dev/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.12.11-h3f84c4b_0_cpython.conda + - conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml-0.17.17-py312h4389bb4_3.conda + - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml.clib-0.2.8-py312h4389bb4_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-1.26.19-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda + - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda + - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda + - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: crates/rattler-bin + subdir: win-64 + - conda: crates/rattler_index + subdir: win-64 packages: - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -582,6 +850,746 @@ packages: license_family: MIT size: 2014904 timestamp: 1737388233822 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-auth-0.9.0-h0fbd49f_19.conda + sha256: 02bb7d2b21f60591944d97c2299be53c9c799085d0a1fb15620d5114cf161c3a + md5: 24139f2990e92effbeb374a0eb33fdb1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 122970 + timestamp: 1753305744902 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-auth-0.9.0-h9972aa3_19.conda + sha256: 386743f3dcfac108bcbb5d1c7e444ca8218284853615a8718a9092d4d71f0a1b + md5: 38551fbfe76020ffd06b3d77889d01f5 + depends: + - __osx >=10.13 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 110717 + timestamp: 1753305752177 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-auth-0.9.0-h9eee66f_19.conda + sha256: 743df69276ea22058299cc028a6bcb2a4bd172ba08de48c702baf4d49fb61c45 + md5: 7b554506535c66852c5090a14801dfb9 + depends: + - __osx >=11.0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 106630 + timestamp: 1753305735994 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-auth-0.9.0-hd9a66b3_19.conda + sha256: d38536adcc9b2907381e0f12cf9f92a831d5991819329d9bf93bcc5dd226417d + md5: 6bed5e0b1d39b4e99598112aff67b968 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 115951 + timestamp: 1753305747891 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-cal-0.9.2-he7b75e1_1.conda + sha256: 30ecca069fdae0aa6a8bb64c47eb5a8d9a7bef7316181e8cbb08b7cb47d8b20f + md5: c04d1312e7feec369308d656c18e7f3e + depends: + - __glibc >=2.17,<3.0.a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - libgcc >=14 + - openssl >=3.5.1,<4.0a0 + license: Apache-2.0 + license_family: Apache + size: 50942 + timestamp: 1752240577225 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-cal-0.9.2-h6f29d6d_1.conda + sha256: 41d60e59a6c906636a6c82b441d10d21a1623fd03188965319572a17e03f4da1 + md5: 44f3a90d7c5a280f68bf1a7614f057b6 + depends: + - __osx >=10.13 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: Apache + size: 40872 + timestamp: 1752240723936 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-cal-0.9.2-hd08b81e_1.conda + sha256: 0cff81daf70f64bb8bf51f0883727d253c0462085f6bfe3d6c619479fbaec329 + md5: f8d75a83ced3f7296fed525502eac257 + depends: + - __osx >=11.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: Apache + size: 41154 + timestamp: 1752240791193 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-cal-0.9.2-hef2a5b8_1.conda + sha256: cd396607f5ffdbdae6995ea135904f6efe7eaac19346aec07359684424819a16 + md5: 096193e01d32724a835517034a6926a2 + depends: + - aws-c-common >=0.12.4,<0.12.5.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + size: 49125 + timestamp: 1752241167516 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-common-0.12.4-hb03c661_0.conda + sha256: 6c9e1b9e82750c39ac0251dcfbeebcbb00a1af07c0d7e3fb1153c4920da316eb + md5: ae5621814cb99642c9308977fe90ed0d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + size: 236420 + timestamp: 1752193614294 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-common-0.12.4-h1c43f85_0.conda + sha256: 94e26ee718358b505aa3c3ddfcedcabd0882de9ff877057985151874b54e9851 + md5: f9547dfb10c15476c17d2d54b61747b8 + depends: + - __osx >=10.13 + license: Apache-2.0 + license_family: Apache + size: 228243 + timestamp: 1752193906883 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-common-0.12.4-h6caf38d_0.conda + sha256: d94c508308340b5b8294d2c382737b72b77e9df688610fa034d0a009a9339d73 + md5: 7a3edd3d065687fe3aa9a04a515fd2bf + depends: + - __osx >=11.0 + license: Apache-2.0 + license_family: Apache + size: 221313 + timestamp: 1752193769784 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-common-0.12.4-hfd05255_0.conda + sha256: c818a09c4d9fe228bb6c94a02c0b05f880ead16ca9f0f59675ae862479ea631a + md5: dcac61b0681b4a2c8e74772415f9e490 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + size: 235039 + timestamp: 1752193765837 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-compression-0.3.1-h92c474e_6.conda + sha256: 154d4a699f4d8060b7f2cec497a06e601cbd5c8cde6736ced0fb7e161bc6f1bb + md5: 3490e744cb8b9d5a3b9785839d618a17 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 22116 + timestamp: 1752240005329 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-compression-0.3.1-h7a4e982_6.conda + sha256: 2029ee55f83e1952ea0c220b0dd30f1b6f9e9411146c659489fcfd6a29af2ddf + md5: 6a4b25acf73532bbec863c2c2ae45842 + depends: + - __osx >=10.13 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 21116 + timestamp: 1752240021842 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-compression-0.3.1-habbe1e8_6.conda + sha256: 633c7ee0e80c24fa6354b2e1c940af6d7f746c9badc3da94681a1a660faeca39 + md5: 35c95aad3ab99e0a428c2e02e8b8e282 + depends: + - __osx >=11.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 21037 + timestamp: 1752240015504 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-compression-0.3.1-ha8a2810_6.conda + sha256: 760d399e54d5f9e86fdc76633e15e00e1b60fc90b15a446b9dce6f79443dcfd7 + md5: f00789373bfeb808ca267a34982352de + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 22931 + timestamp: 1752240036957 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-event-stream-0.5.5-h149bd38_3.conda + sha256: 74b7e5d727505efdb1786d9f4e0250484d23934a1d87f234dacacac97e440136 + md5: f9bff8c2a205ee0f28b0c61dad849a98 + depends: + - libgcc >=14 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + license: Apache-2.0 + license_family: APACHE + size: 57675 + timestamp: 1753199060663 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-event-stream-0.5.5-hf5ae603_3.conda + sha256: f533b662b242fb0b8f001380cdc4fa31f2501c95b31e36d585efdf117913e096 + md5: 87d020af52c47edbd9f5abd9530c3c3a + depends: + - __osx >=10.13 + - libcxx >=19 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 51888 + timestamp: 1753199060561 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-event-stream-0.5.5-hd1b68e1_3.conda + sha256: d1021dfd8a5726af35b73207d90320dd60e85c257af4b4534fecfb34d31751a4 + md5: dc140e52c81171b62d306476b6738220 + depends: + - __osx >=11.0 + - libcxx >=19 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 51020 + timestamp: 1753199075045 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-event-stream-0.5.5-hccb7587_3.conda + sha256: c03c5c77ab447765ab2cfec6d231bafde6a07fc8de19cbb632ca7f849ec8fe29 + md5: cf4d3c01bd6b17c38a4de30ff81d4716 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + license: Apache-2.0 + license_family: APACHE + size: 56295 + timestamp: 1753199087984 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-http-0.10.4-h37a7233_0.conda + sha256: 6794d020d75cafa15e7677508c4bea5e8bca6233a5c7eb6c34397367ee37024c + md5: d828cb0be64d51e27eebe354a2907a98 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-compression >=0.3.1,<0.3.2.0a0 + license: Apache-2.0 + license_family: APACHE + size: 224186 + timestamp: 1753205774708 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-http-0.10.4-hb3df2dd_0.conda + sha256: 59e0d21fee5dbe9fe318d0a697d35e251199755457028f3b8944fd49d5f0450f + md5: 18ce47e0fab1b9b7fb3fea47a34186ad + depends: + - __osx >=10.13 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-compression >=0.3.1,<0.3.2.0a0 + license: Apache-2.0 + license_family: APACHE + size: 191794 + timestamp: 1753205776009 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-http-0.10.4-h09a8a51_0.conda + sha256: 54233587cfd6559e98b2d82c90c3721c059d1dd22518993967fb794e1b8d2d14 + md5: 73e8d2fb68c060de71369ebd5a9b8621 + depends: + - __osx >=11.0 + - aws-c-compression >=0.3.1,<0.3.2.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 170412 + timestamp: 1753205794763 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-http-0.10.4-h04b3cea_0.conda + sha256: 31e65a30b1c99fff0525cc27b5854dc3e3d18a78c13245ea20114f1a503cbd13 + md5: ec4a2bd790833c3ca079d0e656e3c261 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-compression >=0.3.1,<0.3.2.0a0 + license: Apache-2.0 + license_family: APACHE + size: 206269 + timestamp: 1753205802777 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-io-0.21.2-h6252d9a_1.conda + sha256: 01ab3fd74ccd1cd3ebdde72898e0c3b9ab23151b9cd814ac627e3efe88191d8e + md5: cf5e9b21384fdb75b15faf397551c247 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - s2n >=1.5.23,<1.5.24.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 180168 + timestamp: 1753465862916 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-io-0.21.2-h46f635e_1.conda + sha256: 1b44d16454c90c0201e9297ba937fd70c2e86569b18967e932a8dfbbdaee7d37 + md5: eb8c7b3617c0571f3586d57df50b1185 + depends: + - __osx >=10.15 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 181750 + timestamp: 1753465852316 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-io-0.21.2-hc6344be_1.conda + sha256: e872cc4ad2ebb2aee84c1bb8f86e1fb2b5505d8932f560f8dcac6d6436ebca88 + md5: 5b427cbf0259d0a50268901824df6331 + depends: + - __osx >=11.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 175631 + timestamp: 1753465863221 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-io-0.21.2-h20b9e97_1.conda + sha256: 47d3d3cfa9d0628e297a574fb8e124ba32bf2779e8a8b2de26c8c2b30dcad27a + md5: 9b9b649cde9d96dd54b3899a130da1e6 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 181441 + timestamp: 1753465872617 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-mqtt-0.13.3-h19deb91_3.conda + sha256: 4f1b36a50f9d74267cc73740af252f1d6f2da21a6dbef3c0086df1a78c81ed6f + md5: 1680d64986f8263978c3624f677656c8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 216117 + timestamp: 1753306261844 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-mqtt-0.13.3-h6fbeeec_3.conda + sha256: 4bffd41ba1c97f2788f63fb637cd07ea509f7f469f7ae61e997b37bbc8f2f1bb + md5: bbfe8f57e247fabd15227d2c0801cb14 + depends: + - __osx >=10.13 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 188193 + timestamp: 1753306273062 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-mqtt-0.13.3-h625c29d_3.conda + sha256: 129cfcd2132dcc019f85d6259671ed13c0d5d3dfd287ea684bf625503fb8c3b5 + md5: 8937dc148e22c1c15d2f181e6b6eee5e + depends: + - __osx >=11.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 150189 + timestamp: 1753306324109 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-mqtt-0.13.3-h6b158f5_3.conda + sha256: e860df2e337dc0f1deb39f90420233a14de2f38529b7c0add526227a2eef0620 + md5: 16ff5efd5b9219df333171ec891952c1 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 206091 + timestamp: 1753306348261 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-s3-0.8.6-h800fcd2_2.conda + sha256: 886345904f41cdcd8ca4a540161d471d18de60871ffcce42242a4812fc90dcea + md5: 50e0900a33add0c715f17648de6be786 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - openssl >=3.5.1,<4.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 137514 + timestamp: 1753335820784 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-s3-0.8.6-he7aa9d9_2.conda + sha256: 2b25912f0c528e98c6d033908068ca69918dbc0ea4d263b736151a9e3d90064d + md5: 72e2009c8ad840d2f22124aa3dacf931 + depends: + - __osx >=10.13 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 121694 + timestamp: 1753335830764 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-s3-0.8.6-h6ded10d_2.conda + sha256: cd3e9f1ef88e6f77909ddad68d99a620546a94d26ce36c6802a8c04905221cd0 + md5: 19821ae3d32c9d446a899562b35ef89e + depends: + - __osx >=11.0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 117740 + timestamp: 1753335826708 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-s3-0.8.6-h46905be_2.conda + sha256: d91eee836c22436bef1b08ae3137181a9fe92c51803e8710e5e0ac039126f69c + md5: d15a4df142dbd6e39825cdf32025f7e4 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + license: Apache-2.0 + license_family: APACHE + size: 128957 + timestamp: 1753335843139 +- conda: https://prefix.dev/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h92c474e_1.conda + sha256: a9e071a584be0257b2ec6ab6e1f203e9d6b16d2da2233639432727ffbf424f3d + md5: 4ab554b102065910f098f88b40163835 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 59146 + timestamp: 1752240966518 +- conda: https://prefix.dev/conda-forge/osx-64/aws-c-sdkutils-0.2.4-h7a4e982_1.conda + sha256: 85d1b9eb67e02f6a622dcc0c854683da8ccd059d59b80a1ffa7f927eac771b93 + md5: 9ab61d370fc6e4caeb5525ef92e2d477 + depends: + - __osx >=10.13 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 55375 + timestamp: 1752240983413 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-habbe1e8_1.conda + sha256: cab7f54744619b88679c577c9ec8d56957bc8f6829e9966a7e50857fbc6c756d + md5: 9d77627725afb71b57f38355ee9e2829 + depends: + - __osx >=11.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 53149 + timestamp: 1752240972623 +- conda: https://prefix.dev/conda-forge/win-64/aws-c-sdkutils-0.2.4-ha8a2810_1.conda + sha256: b8c7637ad8069ace0f79cc510275b01787c9d478888d4e548980ef2ca61f19c5 + md5: afbb1a7d671fc81c97daeac8ff6c54e0 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 56289 + timestamp: 1752240989872 +- conda: https://prefix.dev/conda-forge/linux-64/aws-checksums-0.2.7-h92c474e_2.conda + sha256: 7168007329dfb1c063cd5466b33a1f2b8a28a00f587a0974d97219432361b4db + md5: 248831703050fe9a5b2680a7589fdba9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 76748 + timestamp: 1752241068761 +- conda: https://prefix.dev/conda-forge/osx-64/aws-checksums-0.2.7-h7a4e982_2.conda + sha256: 523e5d6ffb58a333c6e4501e18120b53290ddad1f879e72ac7f58b15b505f92a + md5: a8a7aa3088b1310cebbc4777f887bd80 + depends: + - __osx >=10.13 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 75320 + timestamp: 1752241080472 +- conda: https://prefix.dev/conda-forge/osx-arm64/aws-checksums-0.2.7-habbe1e8_2.conda + sha256: 648c3d23df53b4cea1d551e4e54a544284be5436af5453296ed8184d970efc3a + md5: f3f6fef7c8d8ce7f80df37e4aaaf6b93 + depends: + - __osx >=11.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 74030 + timestamp: 1752241089866 +- conda: https://prefix.dev/conda-forge/win-64/aws-checksums-0.2.7-ha8a2810_2.conda + sha256: 2c2f5b176fb8c0f15c6bc5edea0b2dd3d56b58e8b1124eb0f592665cec5dfc35 + md5: d6342b48cb2f43df847ee39e0858813a + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 92982 + timestamp: 1752241099189 +- conda: https://prefix.dev/conda-forge/linux-64/awscli-2.28.21-py313h78bf25f_0.conda + sha256: f252f3297508f63aaa57239782e038af69230f9b81f93bc9b07565b92c3940c1 + md5: 8fc2b3f11631573cb3ceabf3c0010302 + depends: + - awscrt 0.27.5 + - colorama >=0.2.5,<0.4.7 + - distro >=1.5.0,<1.9.0 + - docutils >=0.10,<0.20 + - jmespath >=0.7.1,<1.1.0 + - prompt-toolkit >=3.0.24,<3.0.52 + - python >=3.13,<3.14.0a0 + - python-dateutil >=2.1,<=2.9.0 + - python_abi 3.13.* *_cp313 + - ruamel.yaml >=0.15.0,<=0.17.21 + - ruamel.yaml.clib >=0.2.0,<=0.2.12 + - urllib3 >=1.25.4,<1.27 + license: Apache-2.0 + license_family: APACHE + size: 13723050 + timestamp: 1756542009024 +- conda: https://prefix.dev/conda-forge/osx-64/awscli-2.28.21-py312hb401068_0.conda + sha256: 8cd6541faeb849e59cc63cabb3f7dcca2f68e7af9ad1145a0b2addee7dfcc3e3 + md5: 875b918fcb8a580afc13da9113702f96 + depends: + - awscrt 0.27.5 + - colorama >=0.2.5,<0.4.7 + - distro >=1.5.0,<1.9.0 + - docutils >=0.10,<0.20 + - jmespath >=0.7.1,<1.1.0 + - prompt-toolkit >=3.0.24,<3.0.52 + - python >=3.12,<3.13.0a0 + - python-dateutil >=2.1,<=2.9.0 + - python_abi 3.12.* *_cp312 + - ruamel.yaml >=0.15.0,<=0.17.21 + - ruamel.yaml.clib >=0.2.0,<=0.2.12 + - urllib3 >=1.25.4,<1.27 + license: Apache-2.0 + license_family: APACHE + size: 13698162 + timestamp: 1756542149821 +- conda: https://prefix.dev/conda-forge/osx-arm64/awscli-2.28.21-py313h8f79df9_0.conda + sha256: d5baea3efcffbcc3de8ca033e02978a40a8d1835ca8c4da22beaa040c00b8254 + md5: 6f4201b56fce65fc5b498af3312c0c17 + depends: + - awscrt 0.27.5 + - colorama >=0.2.5,<0.4.7 + - distro >=1.5.0,<1.9.0 + - docutils >=0.10,<0.20 + - jmespath >=0.7.1,<1.1.0 + - prompt-toolkit >=3.0.24,<3.0.52 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python-dateutil >=2.1,<=2.9.0 + - python_abi 3.13.* *_cp313 + - ruamel.yaml >=0.15.0,<=0.17.21 + - ruamel.yaml.clib >=0.2.0,<=0.2.12 + - urllib3 >=1.25.4,<1.27 + license: Apache-2.0 + license_family: APACHE + size: 13755438 + timestamp: 1756542519517 +- conda: https://prefix.dev/conda-forge/win-64/awscli-2.28.21-py312h2e8e312_0.conda + sha256: d910becf7a5bf652a64d7f7acda3eee6af05dbee02df90ce46b67c8aa4860df3 + md5: 4f0f5301bbc059b788c18ad07d5c2c5c + depends: + - awscrt 0.27.5 + - colorama >=0.2.5,<0.4.7 + - distro >=1.5.0,<1.9.0 + - docutils >=0.10,<0.20 + - jmespath >=0.7.1,<1.1.0 + - prompt-toolkit >=3.0.24,<3.0.52 + - python >=3.12,<3.13.0a0 + - python-dateutil >=2.1,<=2.9.0 + - python_abi 3.12.* *_cp312 + - ruamel.yaml >=0.15.0,<=0.17.21 + - ruamel.yaml.clib >=0.2.0,<=0.2.12 + - urllib3 >=1.25.4,<1.27 + license: Apache-2.0 + license_family: APACHE + size: 13744855 + timestamp: 1756542252877 +- conda: https://prefix.dev/conda-forge/linux-64/awscrt-0.27.5-py313ha03020b_0.conda + sha256: 59608029a8e952a5e0147a29a1812e2aa009059fbf27a8ff3c1943820904f1b5 + md5: 5e0612b7958699f9e3851adbd252b801 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-c-event-stream >=0.5.5,<0.5.6.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - python_abi 3.13.* *_cp313 + - aws-c-s3 >=0.8.6,<0.8.7.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-mqtt >=0.13.3,<0.13.4.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - s2n >=1.5.23,<1.5.24.0a0 + license: Apache-2.0 + license_family: APACHE + size: 257621 + timestamp: 1753940907520 +- conda: https://prefix.dev/conda-forge/osx-64/awscrt-0.27.5-py312hef7181d_0.conda + sha256: f982737141a124bfd5680eae76d561ac8ddfe854a8c48ed0ebe2b991cef8d9ca + md5: 953b5d278f81b2aa58f5193dcbb1dca4 + depends: + - python + - __osx >=10.13 + - aws-c-mqtt >=0.13.3,<0.13.4.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-c-event-stream >=0.5.5,<0.5.6.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-s3 >=0.8.6,<0.8.7.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - python_abi 3.12.* *_cp312 + - aws-c-io >=0.21.2,<0.21.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 238594 + timestamp: 1753940910016 +- conda: https://prefix.dev/conda-forge/osx-arm64/awscrt-0.27.5-py313h3c677f0_0.conda + sha256: 7810574c90089fa3af5af844d70bd956944e22be1427347125d340acc4adb9a4 + md5: 4245bcbef8c84660748d5f0925364449 + depends: + - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - aws-c-http >=0.10.4,<0.10.5.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-event-stream >=0.5.5,<0.5.6.0a0 + - python_abi 3.13.* *_cp313 + - aws-c-mqtt >=0.13.3,<0.13.4.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-c-s3 >=0.8.6,<0.8.7.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + license: Apache-2.0 + license_family: APACHE + size: 247508 + timestamp: 1753940913665 +- conda: https://prefix.dev/conda-forge/win-64/awscrt-0.27.5-py312h44c0e62_0.conda + sha256: c01c732b448fad070a16ad61ea7c0536f7460a03053e31f50e8fabf1a0cddc5d + md5: b20fd1f5949b6203c35347297ca31437 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-event-stream >=0.5.5,<0.5.6.0a0 + - aws-c-cal >=0.9.2,<0.9.3.0a0 + - aws-c-auth >=0.9.0,<0.9.1.0a0 + - aws-c-mqtt >=0.13.3,<0.13.4.0a0 + - aws-c-common >=0.12.4,<0.12.5.0a0 + - aws-c-s3 >=0.8.6,<0.8.7.0a0 + - aws-checksums >=0.2.7,<0.2.8.0a0 + - python_abi 3.12.* *_cp312 + - aws-c-io >=0.21.2,<0.21.3.0a0 + - aws-c-http >=0.10.4,<0.10.5.0a0 + license: Apache-2.0 + license_family: APACHE + size: 3315593 + timestamp: 1753940927518 - conda: https://prefix.dev/conda-forge/linux-64/binutils-2.43-h4852527_4.conda sha256: 99a94eead18e7704225ac43682cce3f316fd33bc483749c093eaadef1d31de75 md5: 29782348a527eda3ecfc673109d28e93 @@ -638,6 +1646,65 @@ packages: license_family: GPL size: 36046 timestamp: 1752032788780 +- conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda + sha256: b1941426e564d326097ded7af8b525540be219be7a88ca961d58a8d4fc116db2 + md5: bc8624c405856b1d047dd0a81829b08c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.1.0 hb03c661_4 + license: MIT + license_family: MIT + size: 353639 + timestamp: 1756599425945 +- conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.1.0-py312h462f358_4.conda + sha256: f5b7f28d19f21c2f5bd4608b2a075e872727dae8409303f53c756f44044a3a7f + md5: 6ed15514446509f33df546dcc1752eb1 + depends: + - __osx >=10.13 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - libbrotlicommon 1.1.0 h1c43f85_4 + license: MIT + license_family: MIT + size: 369380 + timestamp: 1756600123615 +- conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda + sha256: a6402a7186ace5c3eb21ed4ce50eda3592c44ce38ab4e9a7ddd57d72b1e61fb3 + md5: 9518cd948fc334d66119c16a2106a959 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.1.0 h6caf38d_4 + license: MIT + license_family: MIT + size: 341104 + timestamp: 1756600117644 +- conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.1.0-py312hbb81ca0_4.conda + sha256: f3c7c9b0a41c0ec0c231b92fe944e1ab9e64cf0b4ae9d82e25994d3233baa20c + md5: 3bb5cbb24258cc7ab83126976d36e711 + depends: + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.1.0 hfd05255_4 + license: MIT + license_family: MIT + size: 323090 + timestamp: 1756599941278 - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d md5: 62ee74e96c5ebb0af99386de58cf9553 @@ -774,6 +1841,22 @@ packages: license: ISC size: 158144 timestamp: 1738298224464 +- conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda + sha256: 3b82f62baad3fd33827b01b0426e8203a2786c8f452f633740868296bcbe8485 + md5: c9e0c0f82f6e63323827db462b40ede8 + depends: + - __win + license: ISC + size: 154489 + timestamp: 1754210967212 +- conda: https://prefix.dev/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda + sha256: 837b795a2bb39b75694ba910c13c15fa4998d4bb2a622c214a6a5174b2ae53d1 + md5: 74784ee3d225fc3dca89edb635b4e5cc + depends: + - __unix + license: ISC + size: 154402 + timestamp: 1754210968730 - conda: https://prefix.dev/conda-forge/osx-64/ca-certificates-2025.1.31-h8857fd0_0.conda sha256: 42e911ee2d8808eacedbec46d99b03200a6138b8e8a120bd8acabe1cac41c63b md5: 3418b6c8cac3e71c0bc089fc5ea53042 @@ -1220,6 +2303,15 @@ packages: license_family: BSD size: 15107993 timestamp: 1684462053404 +- conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + size: 27011 + timestamp: 1733218222191 - conda: https://prefix.dev/conda-forge/osx-64/compiler-rt-19.1.7-h52031e2_0.conda sha256: 781b70f7475d387183fba59b5d88e874ec976458b893ec4e8c4c2564944aa680 md5: 8098d99b4c30adb2f9cc18f8584d0b45 @@ -1325,6 +2417,52 @@ packages: license_family: BSD size: 6957 timestamp: 1753098809481 +- conda: https://prefix.dev/conda-forge/noarch/distro-1.8.0-pyhd8ed1ab_0.conda + sha256: 0d01c4da6d4f0a935599210f82ac0630fa9aeb4fc37cbbc78043a932a39ec4f3 + md5: 67999c5465064480fa8016d00ac768f6 + depends: + - python >=3.6 + license: Apache-2.0 + license_family: APACHE + size: 40854 + timestamp: 1675116355989 +- conda: https://prefix.dev/conda-forge/linux-64/docutils-0.18.1-py313h78bf25f_1.conda + sha256: 101d73deed8d839335d75ff2cdcb548ba666b699e16fd11488644efb0644cf7f + md5: 0d72df6ebc7870ee7f7486dde11b7732 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + size: 901056 + timestamp: 1755942635216 +- conda: https://prefix.dev/conda-forge/osx-64/docutils-0.18.1-py312hb401068_1.conda + sha256: 2af045a5ba0b61e77d849c5dca966a7f453eaa26c58d14e5127b95e535adeb07 + md5: 0d62b036556079c60714f0c6ea988302 + depends: + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + size: 896091 + timestamp: 1755942887880 +- conda: https://prefix.dev/conda-forge/osx-arm64/docutils-0.18.1-py313h8f79df9_1.conda + sha256: 18c04895a1f7663cb7f39e6f4cd696d8932ead398ef0c514ccfeaf7372269912 + md5: 3872bbedbb08bc79ad5b78792debb698 + depends: + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + size: 902837 + timestamp: 1755942805232 +- conda: https://prefix.dev/conda-forge/win-64/docutils-0.18.1-py312h2e8e312_1.conda + sha256: 517fe814fbfe570978369bc6dd9f951739293cf90905213204f30b2c29df7946 + md5: 766c498c3e50dac8e4605d6ac9dcf5a8 + depends: + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + size: 897162 + timestamp: 1755942744913 - conda: https://prefix.dev/conda-forge/linux-64/dprint-0.50.0-hb23c6cf_0.conda sha256: 84a7ab17f3d3d50242a28506e599cc06b1ecea8f4f4d5e6e808d6c15d19ba6f7 md5: aa32af075fd0d097fbb7f42a1886611b @@ -1699,6 +2837,15 @@ packages: license_family: MIT size: 11857802 timestamp: 1720853997952 +- conda: https://prefix.dev/conda-forge/noarch/jmespath-1.0.1-pyhd8ed1ab_1.conda + sha256: 3d2f20ee7fd731e3ff55c189db9c43231bc8bde957875817a609c227bcb295c6 + md5: 972bdca8f30147135f951847b30399ea + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 23708 + timestamp: 1733229244590 - conda: https://prefix.dev/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda sha256: a922841ad80bd7b222502e65c07ecb67e4176c4fa5b03678a005f39fcc98be4b md5: ad8527bf134a90e1c9ed35fa0b64318c @@ -2061,6 +3208,15 @@ packages: license_family: Apache size: 527924 timestamp: 1736877256721 +- conda: https://prefix.dev/conda-forge/osx-64/libcxx-21.1.0-h3d58e20_1.conda + sha256: ff2c82c14232cc0ff8038b3d43dace4a792c05a9b01465448445ac52539dee40 + md5: d5bb255dcf8d208f30089a5969a0314b + depends: + - __osx >=10.13 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 572463 + timestamp: 1756698407882 - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda sha256: 776092346da87a2a23502e14d91eb0c32699c4a1522b7331537bd1c3751dcff5 md5: 5b3e1610ff8bd5443476b91d618f5b77 @@ -2070,6 +3226,15 @@ packages: license_family: Apache size: 523505 timestamp: 1736877862502 +- conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-21.1.0-hf598326_1.conda + sha256: 58427116dc1b58b13b48163808daa46aacccc2c79d40000f8a3582938876fed7 + md5: 0fb2c0c9b1c1259bc7db75c1342b1d99 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 568692 + timestamp: 1756698505599 - conda: https://prefix.dev/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_1.conda sha256: d1ee08b0614d8f9bca84aa91b4015c5efa96162fd865590a126544243699dfc6 md5: 0f3f15e69e98ce9b3307c1d8309d1659 @@ -2226,6 +3391,19 @@ packages: license_family: MIT size: 139068 timestamp: 1730967442102 +- conda: https://prefix.dev/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda + sha256: 8432ca842bdf8073ccecf016ccc9140c41c7114dc4ec77ca754551c01f780845 + md5: 3608ffde260281fa641e70d6e34b1b96 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.7.1.* + license: MIT + license_family: MIT + size: 141322 + timestamp: 1752719767870 - conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda sha256: 67a6c95e33ebc763c1adc3455b9a9ecde901850eb2fceb8e646cc05ef3a663da md5: e3eb7806380bc8bcecba6d749ad5f026 @@ -2236,6 +3414,16 @@ packages: license_family: MIT size: 53415 timestamp: 1739260413716 +- conda: https://prefix.dev/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab + md5: ede4673863426c0883c0063d853bbd85 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 57433 + timestamp: 1743434498161 - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.4.6-h281671d_0.conda sha256: 7805fdc536a3da7fb63dc48e040105cd4260c69a1d2bf5804dadd31bde8bab51 md5: b8667b0d0400b8dcb6844d8e06b2027d @@ -2245,6 +3433,15 @@ packages: license_family: MIT size: 47258 timestamp: 1739260651925 +- conda: https://prefix.dev/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda + sha256: 6394b1bc67c64a21a5cc73d1736d1d4193a64515152e861785c44d2cfc49edf3 + md5: 4ca9ea59839a9ca8df84170fab4ceb41 + depends: + - __osx >=10.13 + license: MIT + license_family: MIT + size: 51216 + timestamp: 1743434595269 - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 sha256: 41b3d13efb775e340e4dba549ab5c029611ea6918703096b2eaa9c015c0750ca md5: 086914b672be056eb70fd4285b6783b6 @@ -2272,6 +3469,17 @@ packages: license_family: MIT size: 40830 timestamp: 1739260917585 +- conda: https://prefix.dev/conda-forge/win-64/libffi-3.4.6-h537db12_1.conda + sha256: d3b0b8812eab553d3464bbd68204f007f1ebadf96ce30eb0cbc5159f72e353f5 + md5: 85d8fa5e55ed8f93f874b3b23ed54ec6 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + size: 44978 + timestamp: 1743435053850 - conda: https://prefix.dev/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda sha256: 3a572d031cb86deb541d15c1875aaa097baefc0c580b54dc61f5edab99215792 md5: ef504d1acbd74b7cc6849ef8af47dd03 @@ -2690,6 +3898,18 @@ packages: license: 0BSD size: 104465 timestamp: 1738525557254 +- conda: https://prefix.dev/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + sha256: 55764956eb9179b98de7cc0e55696f2eff8f7b83fc3ebff5e696ca358bca28cc + md5: c15148b2e18da456f5108ccb5e411446 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - xz 5.8.1.* + license: 0BSD + size: 104935 + timestamp: 1749230611612 - conda: https://prefix.dev/conda-forge/linux-64/liblzma-devel-5.6.4-hb9d3cd8_0.conda sha256: 34928b36a3946902196a6786db80c8a4a97f6c9418838d67be90a1388479a682 md5: 5ab1a0df19c8f3ec00d5e63458e0a420 @@ -2746,6 +3966,25 @@ packages: license: 0BSD size: 116244 timestamp: 1749230297170 +- conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee + md5: c7e925f37e3b40d893459e625f6a53f1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: BSD-2-Clause + license_family: BSD + size: 91183 + timestamp: 1748393666725 +- conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 + md5: 85ccccb47823dd9f7a99d2c7f530342f + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + size: 71829 + timestamp: 1748393749336 - conda: https://prefix.dev/conda-forge/linux-64/libnghttp2-1.58.0-h47da74e_1.conda sha256: 1910c5306c6aa5bcbd623c3c930c440e9c77a5a019008e1487810e3c1d3716cb md5: 700ac6ea6d53d5510591c4344d5c989a @@ -2877,6 +4116,16 @@ packages: license: Unlicense size: 915956 timestamp: 1739953155793 +- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda + sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da + md5: 0b367fad34931cb79e0d6b7e5c06bb1c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + size: 932581 + timestamp: 1753948484112 - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda sha256: 859e5f1a39e320b3575b98b7a80ab7c62b337465b12b181c8bbe305fecc9430b md5: 7958168c20fbbc5014e1fbda868ed700 @@ -2886,6 +4135,15 @@ packages: license: Unlicense size: 977598 timestamp: 1739953439197 +- conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda + sha256: 466366b094c3eb4b1d77320530cbf5400e7a10ab33e4824c200147488eebf7a6 + md5: 156bfb239b6a67ab4a01110e6718cbc4 + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: blessing + size: 980121 + timestamp: 1753948554003 - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda sha256: 266639fb10ca92287961574b0b4d6031fa40dd9d723d64a0fcb08513a24dab03 md5: c83357a21092bd952933c36c5cb4f4d6 @@ -2895,6 +4153,16 @@ packages: license: Unlicense size: 898767 timestamp: 1739953312379 +- conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda + sha256: 802ebe62e6bc59fc26b26276b793e0542cfff2d03c086440aeaf72fb8bbcec44 + md5: 1dcb0468f5146e38fae99aef9656034b + depends: + - __osx >=11.0 + - icu >=75.1,<76.0a0 + - libzlib >=1.3.1,<2.0a0 + license: blessing + size: 902645 + timestamp: 1753948599139 - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda sha256: 08669790e4de89201079e93e8a8d8c51a3cd57a19dd559bb0d5bc6c9a7970b99 md5: 88931435901c1f13d4e3a472c24965aa @@ -2905,6 +4173,16 @@ packages: license: Unlicense size: 1081190 timestamp: 1739953491995 +- conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda + sha256: 5dc4f07b2d6270ac0c874caec53c6984caaaa84bc0d3eb593b0edf3dc8492efa + md5: ccb20d946040f86f0c05b644d5eadeca + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + size: 1288499 + timestamp: 1753948889360 - conda: https://prefix.dev/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda sha256: 50e47fd9c4f7bf841a11647ae7486f65220cfc988ec422a4475fe8d5a823824d md5: 1f5a58e686b13bcfde88b93f547d23fe @@ -3386,6 +4664,66 @@ packages: license_family: MIT size: 83849 timestamp: 1737310619948 +- conda: https://prefix.dev/conda-forge/linux-64/minio-client-2025.07.21.05.28.08-hfc2019e_0.conda + sha256: fd4b0e7f78eeb9c8ac3e81d4ac47b762a5bcf89a81cb9f4de7d9f87128610da3 + md5: 123268b66158fe6b737c08badf07522d + license: AGPL-3.0-or-later + license_family: AGPL + size: 20940556 + timestamp: 1753336475749 +- conda: https://prefix.dev/conda-forge/osx-64/minio-client-2025.07.21.05.28.08-hccc6df8_0.conda + sha256: 41ffb10fb94cb41e1314dbc6f3edcdba5c68901b427e0a97f3191b5bee6459a2 + md5: cd3c38fc2e8d4c410d9d368376ce4c6f + constrains: + - __osx >=10.12 + license: AGPL-3.0-or-later + license_family: AGPL + size: 20759192 + timestamp: 1753336532300 +- conda: https://prefix.dev/conda-forge/osx-arm64/minio-client-2025.07.21.05.28.08-h820172f_0.conda + sha256: f9e2aa71de6c58dcc320fb29abb7d6330aa5397a2d987d1f84e7016ecffa7de3 + md5: 284b0a86cfdb402a528e576f7091549e + license: AGPL-3.0-or-later + license_family: AGPL + size: 19334349 + timestamp: 1753336520521 +- conda: https://prefix.dev/conda-forge/win-64/minio-client-2025.07.21.05.28.08-h11686cb_0.conda + sha256: 029eafeebb1efb2b545f122a807e44590454584530530f048cecd54b37ba4c28 + md5: 31e6a6420e24de417433891e2d67efc8 + license: AGPL-3.0-or-later + license_family: AGPL + size: 20826403 + timestamp: 1753336487247 +- conda: https://prefix.dev/conda-forge/linux-64/minio-server-2025.01.20.14.49.07-hbcca054_1.conda + sha256: 64ec235861580506d91aea596e7459afe2587322567246a35b247318606db43b + md5: 0b791bd9c5c9425236b84bc6d5c8680a + license: AGPL-3.0-only + license_family: AGPL + size: 32642210 + timestamp: 1740608434726 +- conda: https://prefix.dev/conda-forge/osx-64/minio-server-2025.01.20.14.49.07-h8857fd0_1.conda + sha256: 0dc2b0b3fd446cbb3d9ee724d2411933d3cbb90d10165a47e4b238d9d93f4643 + md5: f3de9c14bf2ebc2d46329296dc4c5e17 + constrains: + - __osx>=10.12 + license: AGPL-3.0-only + license_family: AGPL + size: 33078891 + timestamp: 1740608504514 +- conda: https://prefix.dev/conda-forge/osx-arm64/minio-server-2025.01.20.14.49.07-hf0a4a13_1.conda + sha256: b845aca8956c646c4e13b257d33d18922f48dae813950735537d7a0772edf71a + md5: 5a8bb8e76f4920d8eedc7bd6444a9c85 + license: AGPL-3.0-only + license_family: AGPL + size: 31639530 + timestamp: 1740608475917 +- conda: https://prefix.dev/conda-forge/win-64/minio-server-2025.01.20.14.49.07-h56e8100_1.conda + sha256: a173a0d3f6dad03b6fcc12bebb92a488da73711da166ed4e4d326fef4cf5beba + md5: 85e2181ceea1e4a95100609be9ff9881 + license: AGPL-3.0-only + license_family: AGPL + size: 33154212 + timestamp: 1740609084606 - conda: https://prefix.dev/conda-forge/linux-64/mold-2.36.0-hff13881_1.conda sha256: 6eacb23d89c13dec2184560728d1351c82c52c1aaba85377f056b8e2d15b6b7d md5: 0274ce570b92fcc1dd23915c526457b4 @@ -3443,6 +4781,62 @@ packages: license: X11 AND BSD-3-Clause size: 797030 timestamp: 1738196177597 +- conda: https://prefix.dev/conda-forge/linux-64/nushell-0.106.1-hb0b18c3_0.conda + sha256: 6d4155e07b0e2e80dba11a8ea1e6dddba8640b487b287bd602924928e95d6e34 + md5: 97acc5a3ddc58e0b5ebd10218c32cbcb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.1,<4.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + size: 9578913 + timestamp: 1753855313062 +- conda: https://prefix.dev/conda-forge/osx-64/nushell-0.106.1-h088d292_0.conda + sha256: 884ec715aeb831837371fadbe4d685bdc139df76045b9270608a021f39265b53 + md5: 16686ceed82b3ee44077fc59f8a1c2c3 + depends: + - __osx >=10.13 + - libcxx >=19 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.1,<4.0a0 + constrains: + - __osx >=10.13 + license: MIT + license_family: MIT + size: 9576373 + timestamp: 1753855501921 +- conda: https://prefix.dev/conda-forge/osx-arm64/nushell-0.106.1-h1b39ca6_0.conda + sha256: 032aa5aa79027538eec197a31cb8095278582b4fb8bf18c6727c37c2dc457db7 + md5: 65dc32f130ca4e43df6aa34672d76f52 + depends: + - __osx >=11.0 + - libcxx >=19 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.1,<4.0a0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 9265972 + timestamp: 1753855652097 +- conda: https://prefix.dev/conda-forge/win-64/nushell-0.106.1-hdab3696_0.conda + sha256: f065a56141d5a07659eb975af9ee939301a77d8021cb88795444c2361d7205b2 + md5: 25a6e8cb1ae00521b1d741a7f06cc012 + depends: + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.1,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + size: 8813615 + timestamp: 1753857173692 - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda sha256: cbf62df3c79a5c2d113247ddea5658e9ff3697b6e741c210656e239ecaf1768f md5: 41adf927e746dc75ecf0ef841c454e48 @@ -3454,6 +4848,17 @@ packages: license_family: Apache size: 2939306 timestamp: 1739301879343 +- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda + sha256: c9f54d4e8212f313be7b02eb962d0cb13a8dae015683a403d3accd4add3e520e + md5: ffffb341206dd0dab0c36053c048d621 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + size: 3128847 + timestamp: 1754465526100 - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.3.0-h87427d6_3.conda sha256: 58ffbdce44ac18c6632a2ce1531d06e3fb2e855d40728ba3a2b709158b9a1c33 md5: ec504fefb403644d893adffb6e7a2dbe @@ -3476,6 +4881,16 @@ packages: license_family: Apache size: 2591479 timestamp: 1739302628009 +- conda: https://prefix.dev/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda + sha256: 8be57a11019666aa481122c54e29afd604405b481330f37f918e9fbcd145ef89 + md5: 22f5d63e672b7ba467969e9f8b740ecd + depends: + - __osx >=10.13 + - ca-certificates + license: Apache-2.0 + license_family: Apache + size: 2743708 + timestamp: 1754466962243 - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.3.0-hfb2fe0b_3.conda sha256: 6f41c163ab57e7499dff092be4498614651f0f6432e12c2b9f06859a8bc39b75 md5: 730f618b008b3c13c1e3f973408ddd67 @@ -3498,6 +4913,16 @@ packages: license_family: Apache size: 2934522 timestamp: 1739301896733 +- conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda + sha256: f6d1c87dbcf7b39fad24347570166dade1c533ae2d53c60a70fa4dc874ef0056 + md5: bcb0d87dfbc199d0a461d2c7ca30b3d8 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + size: 3074848 + timestamp: 1754465710470 - conda: https://prefix.dev/conda-forge/win-64/openssl-3.3.0-h2466b09_3.conda sha256: 11b2513fceb20102bdc7f7656a59005acb9ecd0886b7cbfb9c13c2c953f2429b md5: d7fec5d3bb8fc0c8e266bf1ad350cec5 @@ -3524,6 +4949,18 @@ packages: license_family: Apache size: 8515197 timestamp: 1739304103653 +- conda: https://prefix.dev/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda + sha256: 2413f3b4606018aea23acfa2af3c4c46af786739ab4020422e9f0c2aec75321b + md5: 150d3920b420a27c0848acca158f94dc + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + size: 9275175 + timestamp: 1754467904482 - conda: https://prefix.dev/conda-forge/osx-arm64/pcre2-10.44-h297a79d_2.conda sha256: 83153c7d8fd99cab33c92ce820aa7bfed0f1c94fc57010cf227b6e3c50cb7796 md5: 147c83e5e44780c7492998acbacddf52 @@ -3615,6 +5052,39 @@ packages: license_family: GPL size: 36118 timestamp: 1720806338740 +- conda: https://prefix.dev/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: d17ae9db4dc594267181bd199bf9a551 + depends: + - python >=3.9 + - wcwidth + constrains: + - prompt_toolkit 3.0.51 + license: BSD-3-Clause + license_family: BSD + size: 271841 + timestamp: 1744724188108 +- conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 + depends: + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + size: 21784 + timestamp: 1733217448189 +- conda: https://prefix.dev/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + size: 21085 + timestamp: 1733217331982 - conda: https://prefix.dev/conda-forge/linux-64/python-3.12.9-h9e4cc4f_0_cpython.conda sha256: 64fed5178f1e9c8ac0f572ac0ce37955f5dee7b2bcac665202bc14f1f7dd618a md5: 5665f0079432f8848079c811cdb537d5 @@ -3641,6 +5111,53 @@ packages: license: Python-2.0 size: 31581682 timestamp: 1739521496324 +- conda: https://prefix.dev/conda-forge/linux-64/python-3.13.5-hec9711d_102_cp313.conda + build_number: 102 + sha256: c2cdcc98ea3cbf78240624e4077e164dc9d5588eefb044b4097c3df54d24d504 + md5: 89e07d92cf50743886f41638d58c4328 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 + - libgcc >=13 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.1,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + size: 33273132 + timestamp: 1750064035176 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://prefix.dev/conda-forge/osx-64/python-3.12.11-h9ccd52b_0_cpython.conda + sha256: ebda5b5e8e25976013fdd81b5ba253705b076741d02bdc8ab32763f2afb2c81b + md5: 06049132ecd09d0c1dc3d54d93cf1d5d + depends: + - __osx >=10.13 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 + - liblzma >=5.8.1,<6.0a0 + - libsqlite >=3.50.0,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.0,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 13571569 + timestamp: 1749049058713 - conda: https://prefix.dev/conda-forge/osx-64/python-3.12.9-h9ccd52b_0_cpython.conda sha256: 17d28d74c91b8a6f7844e6dbeec48cc663a81567ecad88ab032c8422d661be7b md5: 0caa16f85e8ed238ab1430691dff1644 @@ -3683,6 +5200,50 @@ packages: license: Python-2.0 size: 12947786 timestamp: 1739520092196 +- conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.5-hf3f3da0_102_cp313.conda + build_number: 102 + sha256: ee1b09fb5563be8509bb9b29b2b436a0af75488b5f1fa6bcd93fe0fba597d13f + md5: 123b7f04e7b8d6fc206cf2d3466f8a4b + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.1,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + size: 12931515 + timestamp: 1750062475020 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://prefix.dev/conda-forge/win-64/python-3.12.11-h3f84c4b_0_cpython.conda + sha256: b69412e64971b5da3ced0fc36f05d0eacc9393f2084c6f92b8f28ee068d83e2e + md5: 6aa5e62df29efa6319542ae5025f4376 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 + - liblzma >=5.8.1,<6.0a0 + - libsqlite >=3.50.0,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 15829289 + timestamp: 1749047682640 - conda: https://prefix.dev/conda-forge/win-64/python-3.12.9-h3f84c4b_0_cpython.conda sha256: 972ef8c58bb1efd058ec70fa957f673e5ad7298d05e501769359f49ae26c7065 md5: f01cb4695ac632a3530200455e31cec5 @@ -3704,6 +5265,144 @@ packages: license: Python-2.0 size: 15963997 timestamp: 1739519811306 +- conda: https://prefix.dev/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + sha256: f3ceef02ac164a8d3a080d0d32f8e2ebe10dd29e3a685d240e38b3599e146320 + md5: 2cf4264fffb9e6eff6031c5b6884d61c + depends: + - python >=3.7 + - six >=1.5 + license: Apache-2.0 + license_family: APACHE + size: 222742 + timestamp: 1709299922152 +- conda: https://prefix.dev/conda-forge/noarch/python_abi-3.12-8_cp312.conda + build_number: 8 + sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809 + md5: c3efd25ac4d74b1584d2f7a57195ddf1 + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + size: 6958 + timestamp: 1752805918820 +- conda: https://prefix.dev/conda-forge/noarch/python_abi-3.13-8_cp313.conda + build_number: 8 + sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 + md5: 94305520c52a4aa3f6c2b1ff6008d9f8 + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + size: 7002 + timestamp: 1752805902938 +- conda: crates/rattler-bin + name: rattler + version: 0.1.0 + build: ha7ad5ef_0 + subdir: linux-64 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + input: + hash: 74431efded7a33191f83e1b5a5029f244ccb9f6c000296f2770af01fdf2deabe + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler-bin + name: rattler + version: 0.1.0 + build: ha7ad5ef_0 + subdir: osx-64 + constrains: + - __osx >=10.13 + license: BSD-3-Clause + input: + hash: 74431efded7a33191f83e1b5a5029f244ccb9f6c000296f2770af01fdf2deabe + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler-bin + name: rattler + version: 0.1.0 + build: ha7ad5ef_0 + subdir: osx-arm64 + constrains: + - __osx >=11.0 + license: BSD-3-Clause + input: + hash: 74431efded7a33191f83e1b5a5029f244ccb9f6c000296f2770af01fdf2deabe + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler-bin + name: rattler + version: 0.1.0 + build: ha7ad5ef_0 + subdir: win-64 + license: BSD-3-Clause + input: + hash: 74431efded7a33191f83e1b5a5029f244ccb9f6c000296f2770af01fdf2deabe + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler_index + name: rattler_index + version: 0.24.12 + build: ha7ad5ef_0 + subdir: linux-64 + constrains: + - __glibc >=2.17 + license: BSD-3-Clause + input: + hash: aa940f198af68efa60f2ba08ee88ec50ac7a9e8120144d6102ce84bc550b369f + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler_index + name: rattler_index + version: 0.24.12 + build: ha7ad5ef_0 + subdir: osx-64 + constrains: + - __osx >=10.13 + license: BSD-3-Clause + input: + hash: aa940f198af68efa60f2ba08ee88ec50ac7a9e8120144d6102ce84bc550b369f + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler_index + name: rattler_index + version: 0.24.12 + build: ha7ad5ef_0 + subdir: osx-arm64 + constrains: + - __osx >=11.0 + license: BSD-3-Clause + input: + hash: aa940f198af68efa60f2ba08ee88ec50ac7a9e8120144d6102ce84bc550b369f + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml +- conda: crates/rattler_index + name: rattler_index + version: 0.24.12 + build: ha7ad5ef_0 + subdir: win-64 + license: BSD-3-Clause + input: + hash: aa940f198af68efa60f2ba08ee88ec50ac7a9e8120144d6102ce84bc550b369f + globs: + - ../../Cargo.toml + - ../Cargo.toml + - Cargo.toml - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c md5: 283b96675859b20a825f8fa30f311446 @@ -3752,6 +5451,110 @@ packages: license_family: MIT size: 176444 timestamp: 1693427792263 +- conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml-0.17.17-py313h536fd9c_3.conda + sha256: 01c354caee3cb50ba9c888006bbdbdf06b8e30bd4d72eb50a65acd7e9d541251 + md5: 267f55b507598cd08f95c268e063443a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ruamel.yaml.clib >=0.1.2 + - setuptools + license: MIT + license_family: MIT + size: 253461 + timestamp: 1728738926914 +- conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml-0.17.17-py312h3d0f464_3.conda + sha256: 6596c84c899658158a1824ab89e149d9e1ee028f7a4f114adb1dc9fcfa272ad4 + md5: 8612c431ff50f397d6e980677578c37f + depends: + - __osx >=10.13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - ruamel.yaml.clib >=0.1.2 + - setuptools + license: MIT + license_family: MIT + size: 251341 + timestamp: 1728738935612 +- conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml-0.17.17-py313h63a2874_3.conda + sha256: 29a8ba2698ec1cc6510b13db0d4a44217201a5152640fb663795308f7969caf7 + md5: 5688dab7282d9038886dfa57e445eeb2 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - ruamel.yaml.clib >=0.1.2 + - setuptools + license: MIT + license_family: MIT + size: 253774 + timestamp: 1728739049255 +- conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml-0.17.17-py312h4389bb4_3.conda + sha256: 882e831ba2ac4db5e0f19cfbd6a69a0714f7ef90accfdb7afdf23ea9c9c391d8 + md5: 12b2c410e9016b6c72641765a1141192 + depends: + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - ruamel.yaml.clib >=0.1.2 + - setuptools + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + size: 247721 + timestamp: 1728739206454 +- conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml.clib-0.2.8-py313h536fd9c_1.conda + sha256: ef739ff0b07df6406efcb49eed327d931d4dfa6072f98def6a0ae700e584a338 + md5: d3400df9c9d0b58368bc0c0fc2591c39 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + size: 144267 + timestamp: 1728724587572 +- conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml.clib-0.2.8-py312h3d0f464_1.conda + sha256: b5ddb73db7ca3d4d8780af1761efb97a5f555ae489f287a91367624d4425f498 + md5: f4c0464f98dabcd65064e89991c3c9c2 + depends: + - __osx >=10.13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: MIT + license_family: MIT + size: 122331 + timestamp: 1728724619287 +- conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.8-py313h63a2874_1.conda + sha256: 8ed7448178b423dbd59cdea422b1fb732c16beacff2cc70f727eff1afd307896 + md5: 34ad7f96e9e4bae5f9a88d0fb04ad557 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + size: 115973 + timestamp: 1728724684349 +- conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml.clib-0.2.8-py312h4389bb4_1.conda + sha256: d5583406ea6d17391294da0a6dadf9a22aad732d1f658f2d6d12fc50b968c0fa + md5: 5758e70a80936d7527f70196685c6695 + depends: + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + size: 108926 + timestamp: 1728725024979 - conda: https://prefix.dev/conda-forge/linux-64/ruff-0.12.3-hf9daec2_0.conda noarch: python sha256: 6122fb3533f971b6cbc804ac38117f88a2d2587b3cace5625ac4f4ea35fcea4c @@ -3908,6 +5711,26 @@ packages: license_family: MIT size: 37041309 timestamp: 1751057508252 +- conda: https://prefix.dev/conda-forge/linux-64/s2n-1.5.23-h8e187f5_0.conda + sha256: 016fe83763bc837beb205732411583179e2aac1cdef40225d4ad5eeb1bc7b837 + md5: edd15d7a5914dc1d87617a2b7c582d23 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - openssl >=3.5.1,<4.0a0 + license: Apache-2.0 + license_family: Apache + size: 383097 + timestamp: 1753407970803 +- conda: https://prefix.dev/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + sha256: 972560fcf9657058e3e1f97186cc94389144b46dbdf58c807ce62e83f977e863 + md5: 4de79c071274a53dcaf2a8c749d1499e + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 748788 + timestamp: 1748804951958 - conda: https://prefix.dev/conda-forge/linux-64/shellcheck-0.10.0-ha770c72_0.conda sha256: 6809031184c07280dcbaed58e15020317226a3ed234b99cb1bd98384ea5be813 md5: 61b19e9e334ddcdf8bb2422ee576549e @@ -3958,6 +5781,16 @@ packages: license_family: MIT size: 210264 timestamp: 1643442231687 +- conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + size: 18455 + timestamp: 1753199211006 - conda: https://prefix.dev/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda sha256: 69ab5804bdd2e8e493d5709eebff382a72fab3e9af6adf93a237ccf8f7dbd624 md5: 460eba7851277ec1fd80a1a24080787a @@ -4035,6 +5868,17 @@ packages: license_family: BSD size: 3318875 timestamp: 1699202167581 +- conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda + sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 + md5: a0116df4f4ed05c303811a837d5b39d8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + size: 3285204 + timestamp: 1748387766691 - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda sha256: 30412b2e9de4ff82d8c2a7e5d06a15f4f4fef1809a72138b6ccb53a33b26faf5 md5: bf830ba5afc507c6232d4ef0fb1a882d @@ -4044,6 +5888,16 @@ packages: license_family: BSD size: 3270220 timestamp: 1699202389792 +- conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda + sha256: b24468006a96b71a5f4372205ea7ec4b399b0f2a543541e86f883de54cd623fc + md5: 9864891a6946c2fe037c02fca7392ab4 + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + size: 3259809 + timestamp: 1748387843735 - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda sha256: 72457ad031b4c048e5891f3f6cb27a53cb479db68a52d965f796910e71a403a8 md5: b50a57ba89c32b62428b71a875291c9b @@ -4053,6 +5907,27 @@ packages: license_family: BSD size: 3145523 timestamp: 1699202432999 +- conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda + sha256: cb86c522576fa95c6db4c878849af0bccfd3264daf0cc40dd18e7f4a7bfced0e + md5: 7362396c170252e7b7b0c8fb37fe9c78 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + size: 3125538 + timestamp: 1748388189063 +- conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 + md5: ebd0e761de9aa879a51d22cc721bd095 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL + license_family: BSD + size: 3466348 + timestamp: 1748388121356 - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda sha256: 2c4e914f521ccb2718946645108c9bd3fc3216ba69aea20c2c3cedbd8db32bb1 md5: fc048363eb8f03cd1737600a5d08aafe @@ -4115,6 +5990,12 @@ packages: license: LicenseRef-Public-Domain size: 122921 timestamp: 1737119101255 +- conda: https://prefix.dev/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a + license: LicenseRef-Public-Domain + size: 122968 + timestamp: 1742727099393 - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda sha256: db8dead3dd30fb1a032737554ce91e2819b43496a0db09927edf01c32b577450 md5: 6797b005cd0f439c4c5c9ac565783700 @@ -4123,6 +6004,37 @@ packages: license: LicenseRef-MicrosoftWindowsSDK10 size: 559710 timestamp: 1728377334097 +- conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + size: 694692 + timestamp: 1756385147981 +- conda: https://prefix.dev/conda-forge/noarch/urllib3-1.26.19-pyhd8ed1ab_0.conda + sha256: 543ebab5241418a4e0d4d9e356ef13e4361504810a067a01481660bb35eb5643 + md5: 6bb37c314b3cc1515dcf086ffe01c46e + depends: + - brotli-python >=1.0.9 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.7 + license: MIT + license_family: MIT + size: 115125 + timestamp: 1718728467518 +- conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda + sha256: cb357591d069a1e6cb74199a8a43a7e3611f72a6caed9faa49dbb3d7a0a98e0b + md5: 28f4ca1e0337d0f27afb8602663c5723 + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + size: 18249 + timestamp: 1753739241465 - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-h5fd82a7_24.conda sha256: 7ce178cf139ccea5079f9c353b3d8415d1d49b0a2f774662c355d3f89163d7b4 md5: 00cf3a61562bd53bd5ea99e6888793d0 @@ -4156,6 +6068,29 @@ packages: license_family: Proprietary size: 756109 timestamp: 1750371459116 +- conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda + sha256: af4b4b354b87a9a8d05b8064ff1ea0b47083274f7c30b4eb96bc2312c9b5f08f + md5: 603e41da40a765fd47995faa021da946 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_31 + constrains: + - vs2015_runtime 14.44.35208.* *_31 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + size: 682424 + timestamp: 1753739239305 +- conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda + sha256: 67b317b64f47635415776718d25170a9a6f9a1218c0f5a6202bfd687e07b6ea4 + md5: a6b1d5c1fc3cb89f88f7179ee6a9afe3 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_31 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + size: 113963 + timestamp: 1753739198723 - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hfef2bbc_24.conda sha256: 09102e0bd283af65772c052d85028410b0c31989b3cd96c260485d28e270836e md5: 117fcc5b86c48f3b322b0722258c7259 @@ -4216,6 +6151,24 @@ packages: license_family: MIT size: 219013 timestamp: 1719460515960 +- conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda + sha256: f21e63e8f7346f9074fd00ca3b079bd3d2fa4d71f1f89d5b6934bf31446dc2a5 + md5: b68980f2495d096e71c7fd9d7ccf63e6 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 32581 + timestamp: 1733231433877 +- conda: https://prefix.dev/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + size: 9555 + timestamp: 1733130678956 - conda: https://prefix.dev/conda-forge/linux-64/xz-5.6.4-hbcc6ac9_0.conda sha256: 91fc251034fa5199919680aa50299296d89da54b2d066fb6e6a60461c17c0c4a md5: bb511c87804cf7220246a3a6efc45c22 diff --git a/pixi.toml b/pixi.toml index e267374a16..6a62cc1b1a 100644 --- a/pixi.toml +++ b/pixi.toml @@ -1,4 +1,4 @@ -[project] +[workspace] name = "rattler" description = "Rust library to install conda environments" authors = [ @@ -10,6 +10,11 @@ authors = [ channels = ["https://prefix.dev/conda-forge"] platforms = ["linux-64", "win-64", "osx-64", "osx-arm64"] license = "BSD-3-Clause" +preview = ["pixi-build"] + +[workspace.build-variants] +# Use the same Rust version everywhere +rust_compiler_version = ["1.88.0"] [tasks] build = "cargo build" @@ -67,3 +72,36 @@ cargo-clippy = "cargo clippy --all-targets --workspace -- -D warnings -Dclippy:: [environments] lint = { features = [ "lint" ], solve-group = "default" } + +#------------------------------ +# Minio E2E test +#------------------------------ +[feature.minio.dependencies] +minio-server = ">=2025.1.20,<2026" +minio-client = ">=2025.7.21,<2026" +rattler_index = { path = "crates/rattler_index" } +rattler = { path = "crates/rattler-bin" } +nushell = ">=0.106.1,<0.107" + +[feature.minio.tasks] +e2e-s3-minio = "nu scripts/e2e/s3-minio.nu" + +[environments.minio] +features = ["minio"] +no-default-feature = true + +#------------------------------ +# S3 E2E test +#------------------------------ +[feature.s3.dependencies] +awscli = ">=2.28.18,<3" +rattler_index = { path = "crates/rattler_index" } +rattler = { path = "crates/rattler-bin" } +nushell = ">=0.106.1,<0.107" + +[feature.s3.tasks] +e2e-s3-aws = "nu scripts/e2e/s3-aws.nu" + +[environments.s3] +features = ["s3"] +no-default-feature = true diff --git a/py-rattler/Cargo.lock b/py-rattler/Cargo.lock index e533e69448..cd581b1eae 100644 --- a/py-rattler/Cargo.lock +++ b/py-rattler/Cargo.lock @@ -259,6 +259,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + [[package]] name = "async-process" version = "2.4.0" @@ -367,9 +373,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.4" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68c2194a190e1efc999612792e25b1ab3abfefe4306494efaaabc25933c0cbe" +checksum = "d025db5d9f52cbc413b167136afb3d8aeea708c0d8884783cf6253be5e22f6f2" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -689,9 +695,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.3" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852b9226cb60b78ce9369022c0df678af1cac231c882d5da97a0c4e03be6e67" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -2814,7 +2820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.2", ] [[package]] @@ -3620,6 +3626,7 @@ dependencies = [ "pyo3-async-runtimes", "pyo3-build-config", "pyo3-file", + "pythonize", "rattler", "rattler_conda_types", "rattler_config", @@ -3629,6 +3636,7 @@ dependencies = [ "rattler_networking", "rattler_package_streaming", "rattler_repodata_gateway", + "rattler_s3", "rattler_shell", "rattler_solve", "rattler_virtual_packages", @@ -3727,6 +3735,16 @@ dependencies = [ "syn", ] +[[package]] +name = "pythonize" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597907139a488b22573158793aa7539df36ae863eba300c75f3a0d65fc475e27" +dependencies = [ + "pyo3", + "serde", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3898,7 +3916,7 @@ dependencies = [ [[package]] name = "rattler" -version = "0.36.1" +version = "0.37.0" dependencies = [ "anyhow", "console", @@ -4059,6 +4077,7 @@ dependencies = [ "rattler_digest", "rattler_networking", "rattler_package_streaming", + "rattler_s3", "reqwest", "rmp-serde", "serde", @@ -4074,7 +4093,7 @@ dependencies = [ [[package]] name = "rattler_lock" -version = "0.23.16" +version = "0.24.0" dependencies = [ "chrono", "file_url", @@ -4137,6 +4156,7 @@ name = "rattler_networking" version = "0.25.11" dependencies = [ "anyhow", + "async-once-cell", "async-trait", "aws-config", "aws-sdk-s3", @@ -4264,6 +4284,21 @@ dependencies = [ "zstd", ] +[[package]] +name = "rattler_s3" +version = "0.1.0" +dependencies = [ + "aws-config", + "aws-credential-types", + "aws-sdk-s3", + "clap", + "rattler_networking", + "serde", + "thiserror 2.0.16", + "tracing", + "url", +] + [[package]] name = "rattler_shell" version = "0.24.10" diff --git a/py-rattler/Cargo.toml b/py-rattler/Cargo.toml index c8df3fecc9..4d8dd2bc1c 100644 --- a/py-rattler/Cargo.toml +++ b/py-rattler/Cargo.toml @@ -28,6 +28,7 @@ chrono = { version = "0.4" } futures = "0.3.31" parking_lot = { version = "0.12.3", features = ["arc_lock", "send_guard"] } +rattler_s3 = { path = "../crates/rattler_s3", features = ["serde"]} rattler = { path = "../crates/rattler", default-features = false, features = [ "indicatif", ] } @@ -58,6 +59,7 @@ pyo3 = { version = "0.25.1", features = [ "chrono", ] } pyo3-async-runtimes = { version = "0.25.0", features = ["tokio-runtime"] } +pythonize = "0.25.0" tokio = { version = "1.46.1" } reqwest = { version = "0.12.15", default-features = false } diff --git a/py-rattler/rattler/index/__init__.py b/py-rattler/rattler/index/__init__.py index 060834cbba..60fb824c6e 100644 --- a/py-rattler/rattler/index/__init__.py +++ b/py-rattler/rattler/index/__init__.py @@ -1,3 +1,3 @@ -from rattler.index.index import index_fs, index_s3 +from rattler.index.index import index_fs, index_s3, S3Credentials -__all__ = ["index_s3", "index_fs"] +__all__ = ["index_s3", "index_fs", "S3Credentials"] diff --git a/py-rattler/rattler/index/index.py b/py-rattler/rattler/index/index.py index 997181b4ed..d93d3bb464 100644 --- a/py-rattler/rattler/index/index.py +++ b/py-rattler/rattler/index/index.py @@ -1,12 +1,36 @@ from __future__ import annotations +from dataclasses import dataclass import os -from typing import Optional +from typing import Optional, Literal from rattler.platform import Platform from rattler.rattler import py_index_fs, py_index_s3 +@dataclass +class S3Credentials: + """Credentials for accessing an S3 backend.""" + + # The endpoint URL of the S3 backend + endpoint_url: str + + # The region of the S3 backend + region: str + + # The access key ID for the S3 bucket. + access_key_id: Optional[str] = None + + # The secret access key for the S3 bucket. + secret_access_key: Optional[str] = None + + # The session token for the S3 bucket. + session_token: Optional[str] = None + + # Defines how to address the bucket, either using virtual-hosted-style or path-style. + addressing_style: Literal["path", "virtual-host"] = "virtual-host" + + async def index_fs( channel_directory: os.PathLike[str], target_platform: Optional[Platform] = None, @@ -46,12 +70,7 @@ async def index_fs( async def index_s3( channel_url: str, - region: str, - endpoint_url: str, - force_path_style: bool = False, - access_key_id: Optional[str] = None, - secret_access_key: Optional[str] = None, - session_token: Optional[str] = None, + credentials: Optional[S3Credentials] = None, target_platform: Optional[Platform] = None, repodata_patch: Optional[str] = None, write_zst: bool = True, @@ -69,12 +88,8 @@ async def index_s3( Arguments: channel_url: An S3 URL (e.g., s3://my-bucket/my-channel that containins the subdirectories of dependencies to index. - region: The region of the S3 bucket. - endpoint_url: The endpoint URL of the S3 bucket. - force_path_style: Whether to use path-style addressing for S3. - access_key_id: The access key ID to use for authentication. - secret_access_key: The secret access key to use for authentication. - session_token: The session token to use for authentication. + credentials: The credentials to use for accessing the S3 bucket. If not provided, will use the default + credentials from the environment. target_platform: A `Platform` to index dependencies for. repodata_patch: The name of the conda package (expected to be in the `noarch` subdir) that should be used for repodata patching. write_zst: Whether to write repodata.json.zst. @@ -84,12 +99,7 @@ async def index_s3( """ await py_index_s3( channel_url, - region, - endpoint_url, - force_path_style, - access_key_id, - secret_access_key, - session_token, + credentials, target_platform._inner if target_platform else target_platform, repodata_patch, write_zst, diff --git a/py-rattler/src/error.rs b/py-rattler/src/error.rs index f1b05d045b..1c209f9d8e 100644 --- a/py-rattler/src/error.rs +++ b/py-rattler/src/error.rs @@ -1,5 +1,6 @@ use std::{error::Error, io}; +use pyo3::exceptions::PyValueError; use pyo3::{create_exception, exceptions::PyException, PyErr}; use rattler::install::TransactionError; use rattler_conda_types::{ @@ -85,6 +86,8 @@ pub enum PyRattlerError { InvalidHeaderNameError(#[from] reqwest::header::InvalidHeaderName), #[error(transparent)] InvalidHeaderValueError(#[from] reqwest::header::InvalidHeaderValue), + #[error(transparent)] + FromSdkError(#[from] rattler_s3::FromSDKError), } fn pretty_print_error(mut err: &dyn Error) -> String { @@ -185,6 +188,7 @@ impl From for PyErr { PyRattlerError::InvalidHeaderValueError(err) => { InvalidHeaderValueError::new_err(pretty_print_error(&err)) } + PyRattlerError::FromSdkError(err) => PyValueError::new_err(pretty_print_error(&err)), } } } diff --git a/py-rattler/src/index.rs b/py-rattler/src/index.rs index 3bc0f703ee..7b74a37707 100644 --- a/py-rattler/src/index.rs +++ b/py-rattler/src/index.rs @@ -5,9 +5,12 @@ use rattler_config::config::concurrency::default_max_concurrent_solves; use rattler_index::{index_fs, index_s3, IndexFsConfig, IndexS3Config}; use url::Url; -use std::path::PathBuf; - use crate::{error::PyRattlerError, platform::PyPlatform}; +use pyo3::exceptions::PyValueError; +use pythonize::depythonize; +use rattler_networking::AuthenticationStorage; +use rattler_s3::{ResolvedS3Credentials, S3Credentials}; +use std::path::PathBuf; #[pyfunction] #[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] @@ -41,35 +44,44 @@ pub fn py_index_fs( #[pyfunction] #[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] -#[pyo3(signature = (channel_url, region, endpoint_url, force_path_style, access_key_id=None,secret_access_key=None, session_token=None, target_platform=None, repodata_patch=None, write_zst=true, write_shards=true, force=false, max_parallel=None))] -pub fn py_index_s3( - py: Python<'_>, +#[pyo3(signature = (channel_url, credentials=None, target_platform=None, repodata_patch=None, write_zst=true, write_shards=true, force=false, max_parallel=None))] +pub fn py_index_s3<'py>( + py: Python<'py>, channel_url: String, - region: String, - endpoint_url: String, - force_path_style: bool, - access_key_id: Option, - secret_access_key: Option, - session_token: Option, + credentials: Option>, target_platform: Option, repodata_patch: Option, write_zst: bool, write_shards: bool, force: bool, max_parallel: Option, -) -> PyResult> { +) -> PyResult> { let channel_url = Url::parse(&channel_url).map_err(PyRattlerError::from)?; - let endpoint_url = Url::parse(&endpoint_url).map_err(PyRattlerError::from)?; + let credentials = match credentials { + Some(dict) => { + let credentials: S3Credentials = depythonize(&dict)?; + let auth_storage = + AuthenticationStorage::from_env_and_defaults().map_err(PyRattlerError::from)?; + Some((credentials, auth_storage)) + } + None => None, + }; let target_platform = target_platform.map(Platform::from); future_into_py(py, async move { + // Resolve the credentials + let credentials = + match credentials { + Some((credentials, auth_storage)) => credentials + .resolve(&channel_url, &auth_storage) + .ok_or_else(|| PyValueError::new_err("could not resolve s3 credentials"))?, + None => ResolvedS3Credentials::from_sdk() + .await + .map_err(PyRattlerError::from)?, + }; + index_s3(IndexS3Config { channel: channel_url, - region, - endpoint_url, - force_path_style, - access_key_id, - secret_access_key, - session_token, + credentials, target_platform, repodata_patch, write_zst, diff --git a/py-rattler/tests/unit/test_index.py b/py-rattler/tests/unit/test_index.py index 90769a1032..7b490f3e6f 100644 --- a/py-rattler/tests/unit/test_index.py +++ b/py-rattler/tests/unit/test_index.py @@ -11,6 +11,8 @@ from rattler import Platform from rattler.index import index_fs, index_s3 +from rattler.index.index import S3Credentials + # ------------------------------------ FILESYSTEM ------------------------------------ # @@ -130,11 +132,14 @@ async def test_index_s3( # Run index command await index_s3( channel_url=s3_channel, - region=s3_config.region, - endpoint_url=s3_config.endpoint_url, - force_path_style=True, - access_key_id=s3_config.access_key_id, - secret_access_key=s3_config.secret_access_key, + credentials=S3Credentials( + region=s3_config.region, + endpoint_url=s3_config.endpoint_url, + access_key_id=s3_config.access_key_id, + secret_access_key=s3_config.secret_access_key, + session_token=s3_config.session_token, + addressing_style="path", + ), repodata_patch=None, force=True, ) diff --git a/scripts/e2e/s3-aws.nu b/scripts/e2e/s3-aws.nu new file mode 100644 index 0000000000..27f1709535 --- /dev/null +++ b/scripts/e2e/s3-aws.nu @@ -0,0 +1,45 @@ +#!/usr/bin/env nu + +let bucket_name = ($env.BUCKET? | default $"tmp-conda-rattler-(random int 0..1000000)") +let region = ($env.AWS_REGION? | default "eu-west-1") + +def run [desc: string, cmd: closure] { + print $"== ($desc)" + try { do $cmd } catch { } # run as-is + let code = ($env.LAST_EXIT_CODE? | default 0) # capture before any pipes + if $code != 0 { + print $"WARN: ($desc) failed \(exit=($code)\)" + false + } else { true } +} + +def bucket_exists [] { + (do { ^aws s3api head-bucket --bucket $bucket_name } | complete).exit_code == 0 +} + +# --- steps (don’t abort on failure) --- +let test_ok = (run $"Create bucket ($bucket_name)" { + ^aws s3api create-bucket --bucket $bucket_name --create-bucket-configuration $"LocationConstraint=($region)" +}) and (run "Set lifecycle (1 day)" { + ^aws s3api put-bucket-lifecycle-configuration --bucket $bucket_name --lifecycle-configuration '{ "Rules":[{"ID":"ttl-1d","Status":"Enabled","Expiration":{"Days":1},"Filter":{"Prefix":""}}] }' +}) and (run "Upload package" { + ^rattler upload s3 --channel $"s3://($bucket_name)" test-data/packages/empty-0.1.0-h4616a5c_0.conda +}) and (run "Index channel" { + ^rattler-index s3 $"s3://($bucket_name)" +}) and (run "Dry-run install" { + ^rattler create --dry-run -c $"s3://($bucket_name)" empty==0.1.0 +}) + +# --- cleanup always attempted --- +if (bucket_exists) { + print "== Cleanup: remove bucket and all its contents" + (do { ^aws s3 rm $"s3://($bucket_name)" --recursive } | complete) | ignore + (do { ^aws s3 rb $"s3://($bucket_name)" } | complete) | ignore +} else { + print "== Cleanup: bucket did not exist (skip)" +} + +# --- exit non-zero if any step failed --- +if not $test_ok { + exit 1 +} diff --git a/scripts/e2e/s3-minio.nu b/scripts/e2e/s3-minio.nu new file mode 100644 index 0000000000..045aaefee6 --- /dev/null +++ b/scripts/e2e/s3-minio.nu @@ -0,0 +1,79 @@ +#!/usr/bin/env nu + +# Paths +let tmp = ($env.RUNNER_TEMP? | default $env.TEMP? | default "/tmp") +let bin_dir = $tmp +let data_dir = $"($tmp)/minio-data" +let log_file = $"($tmp)/minio.log" +let pid_file = $"($tmp)/minio.pid" +let bucket_name = $"tmp-(random int 0..1000000)" + +# Create directories +mkdir $bin_dir $data_dir + +# Credentials +let root_user = ($env.MINIO_ACCESS_KEY? | default "minio") +let root_password = ($env.MINIO_SECRET_KEY? | default "minio123") + +# Start MinIO in background as a job +print "== Starting Minio server..." +let minio_job = job spawn { + with-env { + MINIO_ROOT_USER: $root_user + MINIO_ROOT_PASSWORD: $root_password + } { + ^minio server $data_dir --address ":9000" out+err> $log_file + } +} + +# wait up to 120s (60 × 2s) for MinIO to be ready +if not (seq 0 59 | any {|_| + try { http get http://localhost:9000/minio/health/live | ignore; true } catch { sleep 2sec; false } +}) { + error make {msg: "MinIO failed to start within 120 seconds"} +} +print "Minio server is up and running..." + +# Configure mc client and bucket +print $"== Configuring bucket ($bucket_name)..." +^mc alias set minio http://localhost:9000 $root_user $root_password +^mc mb $"minio/($bucket_name)" +^mc policy set download $"minio/($bucket_name)" + +print "== Upload packages to Minio" +(^rattler + upload s3 + --channel $"s3://($bucket_name)" + --access-key-id $root_user + --secret-access-key $root_password + --region "us-east-1" + --endpoint-url "http://localhost:9000" + --addressing-style path + test-data/packages/empty-0.1.0-h4616a5c_0.conda +) + +print "== Index the channel" +(^rattler-index + s3 + $"s3://($bucket_name)" + --access-key-id $root_user + --secret-access-key $root_password + --region "us-east-1" + --endpoint-url "http://localhost:9000" + --addressing-style path +) + +print "== Test package can be installed from the channel ===" +with-env { + AWS_ACCESS_KEY_ID: $root_user + AWS_SECRET_ACCESS_KEY: $root_password + AWS_REGION: "us-east-1" + AWS_ENDPOINT_URL: "http://localhost:9000" +} { + (^rattler + create + --dry-run + -c $"s3://($bucket_name)" + empty==0.1.0 + ) +}