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 remote_config = config.remote_config.clone().unwrap_or_default();
63    let sdk_config = remote_config.sdk_config(Some(retry)).await;
64
65    let result = if config.dry {
66        dry::DeployOutput::new(config, &name, &sdk_config, &archive).map(DeployResult::Dry)
67    } else if config.extension {
68        extensions::deploy(config, &name, &sdk_config, &archive, &progress)
69            .await
70            .map(DeployResult::Extension)
71    } else {
72        functions::deploy(config, &name, &sdk_config, &archive, &progress)
73            .await
74            .map(DeployResult::Function)
75    };
76
77    progress.finish_and_clear();
78    let output = result?;
79
80    match &config.output_format() {
81        OutputFormat::Text => println!("{output}"),
82        OutputFormat::Json => {
83            let text = to_string_pretty(&output)
84                .into_diagnostic()
85                .wrap_err("failed to serialize output into json")?;
86            println!("{text}")
87        }
88    }
89
90    Ok(())
91}
92
93fn load_archive(config: &Deploy, metadata: &CargoMetadata) -> Result<(String, BinaryArchive)> {
94    match &config.binary_path {
95        Some(bp) if bp.is_dir() => Err(miette::miette!("invalid file {:?}", bp)),
96        Some(bp) => {
97            let name = match &config.name {
98                Some(name) => name.clone(),
99                None => bp
100                    .file_name()
101                    .and_then(|s| s.to_str())
102                    .map(String::from)
103                    .ok_or_else(|| miette::miette!("invalid binary path {:?}", bp))?,
104            };
105
106            let destination = bp
107                .parent()
108                .ok_or_else(|| miette::miette!("invalid binary path {:?}", bp))?;
109
110            let data = BinaryData::new(&name, config.extension, config.internal);
111            let arc = zip_binary(bp, destination, &data, config.include.clone())?;
112            Ok((name, arc))
113        }
114        None => {
115            let name = match (&config.name, &config.binary_name) {
116                (Some(name), _) => name.clone(),
117                (None, Some(bn)) => bn.clone(),
118                (None, None) => main_binary_from_metadata(metadata)?,
119            };
120            let binary_name = binary_name_or_default(config, &name);
121            let data = BinaryData::new(&binary_name, config.extension, config.internal);
122
123            let arc = create_binary_archive(
124                Some(metadata),
125                &config.lambda_dir,
126                &data,
127                config.include.clone(),
128            )?;
129            Ok((name, arc))
130        }
131    }
132}
133
134pub(crate) fn binary_name_or_default(config: &Deploy, name: &str) -> String {
135    config
136        .binary_name
137        .clone()
138        .unwrap_or_else(|| name.to_string())
139}
140
141#[cfg(test)]
142mod tests {
143    use assertables::assert_contains;
144    use std::path::PathBuf;
145
146    use cargo_lambda_metadata::cargo::load_metadata;
147
148    use super::*;
149
150    #[test]
151    fn test_load_archive_from_binary_path() {
152        let mut config = Deploy::default();
153        config.binary_path = Some(PathBuf::from("../../tests/binaries/binary-x86-64"));
154        config.include = Some(vec!["src".into()]);
155
156        let metadata = load_metadata("../../tests/fixtures/examples-package/Cargo.toml").unwrap();
157        let (name, archive) = load_archive(&config, &metadata).unwrap();
158        assert_eq!(name, "binary-x86-64");
159
160        let files = archive.list().unwrap();
161        assert_contains!(files, &"bootstrap".to_string());
162        assert_contains!(files, &"src/dry.rs".to_string());
163        assert_contains!(files, &"src/extensions.rs".to_string());
164        assert_contains!(files, &"src/functions.rs".to_string());
165        assert_contains!(files, &"src/lib.rs".to_string());
166        assert_contains!(files, &"src/roles.rs".to_string());
167    }
168}