Skip to main content

anodizer_stage_blob/
lib.rs

1//! Blob stage — uploads release artifacts to S3 / GCS / Azure Blob.
2//!
3//! - [`Provider`] — `s3` / `gs` / `azblob` selection.
4//! - [`BlobStage`] — the [`anodizer_core::stage::Stage`] driver. Each config is
5//!   prepared serially (template render, store build, KMS preflight) before the
6//!   parallel upload, so credential/KMS errors surface before any bytes leave.
7
8mod kms;
9mod provider;
10pub mod publisher;
11mod run;
12mod store;
13mod upload;
14
15#[cfg(test)]
16mod tests;
17
18pub use provider::Provider;
19pub use publisher::BlobPublisher;
20pub use run::BlobStage;
21
22/// Environment requirements for the blob stage, derived per `blobs:` entry:
23///
24/// * `s3` with a custom `endpoint` (MinIO-style) — the static keypair
25///   (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`), any env vars the
26///   endpoint template references, and reachability of the rendered
27///   endpoint. Plain AWS (and `gs` / `azblob`) declare no credential
28///   requirement: those providers resolve ambient chains (instance
29///   metadata, workload identity, profiles) that are not env-observable,
30///   and requiring env vars there would false-fail valid setups.
31/// * a `kms_key` using a CLI-backed scheme — the matching cloud CLI
32///   (`aws` / `gcloud` / `az`).
33pub fn env_requirements(
34    ctx: &anodizer_core::context::Context,
35) -> Vec<anodizer_core::EnvRequirement> {
36    use anodizer_core::env_preflight::template_env_refs;
37    let mut out = Vec::new();
38    for c in anodizer_core::env_preflight::crate_universe(&ctx.config) {
39        for b in c.blobs.iter().flatten() {
40            // Unknown providers are config-validation territory, not preflight's.
41            let Ok(provider) = Provider::parse(&b.provider) else {
42                continue;
43            };
44            if provider == Provider::S3
45                && let Some(endpoint) = b.endpoint.as_deref()
46            {
47                out.push(anodizer_core::EnvRequirement::EnvAllOf {
48                    vars: vec![
49                        "AWS_ACCESS_KEY_ID".to_string(),
50                        "AWS_SECRET_ACCESS_KEY".to_string(),
51                    ],
52                });
53                let refs = template_env_refs(endpoint);
54                if !refs.is_empty() {
55                    out.push(anodizer_core::EnvRequirement::EnvAllOf { vars: refs });
56                }
57                if let Ok(rendered) = anodizer_core::template::render(endpoint, ctx.template_vars())
58                    && !rendered.trim().is_empty()
59                {
60                    out.push(anodizer_core::EnvRequirement::Endpoint { url: rendered });
61                }
62            }
63            if let Some(kms_key) = b.kms_key.as_deref() {
64                let tool = match crate::kms::parse_kms_provider(kms_key) {
65                    crate::kms::KmsProvider::Aws => Some("aws"),
66                    crate::kms::KmsProvider::Gcp => Some("gcloud"),
67                    crate::kms::KmsProvider::Azure => Some("az"),
68                    _ => None,
69                };
70                if let Some(tool) = tool {
71                    out.push(anodizer_core::EnvRequirement::Tool {
72                        name: tool.to_string(),
73                    });
74                }
75            }
76        }
77    }
78    out
79}