cargo_lambda_deploy/
lib.rs1use 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}