cargo_lambda_deploy/
lib.rs

1use aws_smithy_types::retry::{RetryConfig, RetryMode};
2use cargo_lambda_build::{BinaryArchive, BinaryData, create_binary_archive, zip_binary};
3use cargo_lambda_interactive::progress::Progress;
4use cargo_lambda_metadata::cargo::{
5    CargoMetadata,
6    deploy::{Deploy, OutputFormat},
7    main_binary_from_metadata,
8};
9use miette::{IntoDiagnostic, Result, WrapErr};
10use serde::Serialize;
11use serde_json::ser::to_string_pretty;
12use std::time::Duration;
13
14mod dry;
15mod extensions;
16mod functions;
17mod roles;
18
19#[derive(Serialize)]
20#[serde(untagged)]
21#[allow(clippy::large_enum_variant)]
22enum DeployResult {
23    Extension(extensions::DeployOutput),
24    Function(functions::DeployOutput),
25    Dry(dry::DeployOutput),
26}
27
28impl std::fmt::Display for DeployResult {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            DeployResult::Extension(o) => o.fmt(f),
32            DeployResult::Function(o) => o.fmt(f),
33            DeployResult::Dry(o) => o.fmt(f),
34        }
35    }
36}
37
38#[tracing::instrument(target = "cargo_lambda")]
39pub async fn run(config: &Deploy, metadata: &CargoMetadata) -> Result<()> {
40    tracing::trace!("deploying project");
41
42    if config.function_config.enable_function_url && config.function_config.disable_function_url {
43        return Err(miette::miette!(
44            "invalid options: --enable-function-url and --disable-function-url cannot be set together"
45        ));
46    }
47
48    let progress = Progress::start("loading binary data");
49    let (name, archive) = match load_archive(config, metadata) {
50        Ok(arc) => arc,
51        Err(err) => {
52            progress.finish_and_clear();
53            return Err(err);
54        }
55    };
56
57    let retry = RetryConfig::standard()
58        .with_retry_mode(RetryMode::Adaptive)
59        .with_max_attempts(3)
60        .with_initial_backoff(Duration::from_secs(5));
61
62    let sdk_config = config.remote_config.sdk_config(Some(retry)).await;
63
64    let result = if config.dry {
65        dry::DeployOutput::new(config, &name, &archive).map(DeployResult::Dry)
66    } else if config.extension {
67        extensions::deploy(config, &name, &sdk_config, &archive, &progress)
68            .await
69            .map(DeployResult::Extension)
70    } else {
71        functions::deploy(config, &name, &sdk_config, &archive, &progress)
72            .await
73            .map(DeployResult::Function)
74    };
75
76    progress.finish_and_clear();
77    let output = result?;
78
79    match &config.output_format() {
80        OutputFormat::Text => println!("{output}"),
81        OutputFormat::Json => {
82            let text = to_string_pretty(&output)
83                .into_diagnostic()
84                .wrap_err("failed to serialize output into json")?;
85            println!("{text}")
86        }
87    }
88
89    Ok(())
90}
91
92fn load_archive(config: &Deploy, metadata: &CargoMetadata) -> Result<(String, BinaryArchive)> {
93    match &config.binary_path {
94        Some(bp) if bp.is_dir() => Err(miette::miette!("invalid file {:?}", bp)),
95        Some(bp) => {
96            let name = match &config.name {
97                Some(name) => name.clone(),
98                None => bp
99                    .file_name()
100                    .and_then(|s| s.to_str())
101                    .map(String::from)
102                    .ok_or_else(|| miette::miette!("invalid binary path {:?}", bp))?,
103            };
104
105            let destination = bp
106                .parent()
107                .ok_or_else(|| miette::miette!("invalid binary path {:?}", bp))?;
108
109            let data = BinaryData::new(&name, config.extension, config.internal);
110            let arc = zip_binary(bp, destination, &data, config.include.clone())?;
111            Ok((name, arc))
112        }
113        None => {
114            let name = match (&config.name, &config.binary_name) {
115                (Some(name), _) => name.clone(),
116                (None, Some(bn)) => bn.clone(),
117                (None, None) => main_binary_from_metadata(metadata)?,
118            };
119            let binary_name = binary_name_or_default(config, &name);
120            let data = BinaryData::new(&binary_name, config.extension, config.internal);
121
122            let arc = create_binary_archive(
123                Some(metadata),
124                &config.lambda_dir,
125                &data,
126                config.include.clone(),
127            )?;
128            Ok((name, arc))
129        }
130    }
131}
132
133pub(crate) fn binary_name_or_default(config: &Deploy, name: &str) -> String {
134    config
135        .binary_name
136        .clone()
137        .unwrap_or_else(|| name.to_string())
138}
139
140#[cfg(test)]
141mod tests {
142    use assertables::assert_contains;
143    use std::path::PathBuf;
144
145    use cargo_lambda_metadata::cargo::load_metadata;
146
147    use super::*;
148
149    #[test]
150    fn test_load_archive_from_binary_path() {
151        let mut config = Deploy::default();
152        config.binary_path = Some(PathBuf::from("../../tests/binaries/binary-x86-64"));
153        config.include = Some(vec!["src".into()]);
154
155        let metadata = load_metadata("../../tests/fixtures/examples-package/Cargo.toml").unwrap();
156        let (name, archive) = load_archive(&config, &metadata).unwrap();
157        assert_eq!(name, "binary-x86-64");
158
159        let files = archive.list().unwrap();
160        assert_contains!(files, &"bootstrap".to_string());
161        assert_contains!(files, &"src/dry.rs".to_string());
162        assert_contains!(files, &"src/extensions.rs".to_string());
163        assert_contains!(files, &"src/functions.rs".to_string());
164        assert_contains!(files, &"src/lib.rs".to_string());
165        assert_contains!(files, &"src/roles.rs".to_string());
166    }
167}