cargo_lambda_remote/
lib.rs1use aws_config::{
2 BehaviorVersion,
3 meta::region::RegionProviderChain,
4 profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider},
5 provider_config::ProviderConfig,
6 retry::RetryConfig,
7};
8use aws_types::{SdkConfig, region::Region};
9use clap::Args;
10use serde::{Deserialize, Serialize, ser::SerializeStruct};
11pub mod tls;
12
13const DEFAULT_REGION: &str = "us-east-1";
14
15#[derive(Args, Clone, Debug, Default, Deserialize, Serialize)]
16pub struct RemoteConfig {
17 #[arg(short, long)]
19 #[serde(default)]
20 pub profile: Option<String>,
21
22 #[arg(short, long)]
24 #[serde(default)]
25 pub region: Option<String>,
26
27 #[arg(short, long, alias = "qualifier")]
29 #[serde(default)]
30 pub alias: Option<String>,
31
32 #[arg(long, default_value = "1")]
34 #[serde(default)]
35 pub retry_attempts: Option<u32>,
36
37 #[arg(long)]
39 #[serde(default)]
40 pub endpoint_url: Option<String>,
41}
42
43impl RemoteConfig {
44 fn retry_policy(&self) -> RetryConfig {
45 let attempts = self.retry_attempts.unwrap_or(1);
46 RetryConfig::standard().with_max_attempts(attempts)
47 }
48
49 pub async fn sdk_config(&self, retry: Option<RetryConfig>) -> SdkConfig {
50 let explicit_region = self.region.clone().map(Region::new);
51
52 let region_provider = RegionProviderChain::first_try(explicit_region.clone())
53 .or_default_provider()
54 .or_else(Region::new(DEFAULT_REGION));
55
56 let retry = retry.unwrap_or_else(|| self.retry_policy());
57 let mut config_loader = if let Some(ref endpoint_url) = self.endpoint_url {
58 aws_config::defaults(BehaviorVersion::latest())
59 .endpoint_url(endpoint_url)
60 .region(region_provider)
61 .retry_config(retry)
62 } else {
63 aws_config::defaults(BehaviorVersion::latest())
64 .region(region_provider)
65 .retry_config(retry)
66 };
67
68 if let Some(profile) = &self.profile {
69 let profile_region = ProfileFileRegionProvider::builder()
70 .profile_name(profile)
71 .build();
72
73 let region_provider =
74 RegionProviderChain::first_try(explicit_region).or_else(profile_region);
75 let region = region_provider.region().await;
76
77 let conf = ProviderConfig::default().with_region(region);
78
79 let creds_provider = ProfileFileCredentialsProvider::builder()
80 .profile_name(profile)
81 .configure(&conf)
82 .build();
83
84 config_loader = config_loader
85 .region(region_provider)
86 .credentials_provider(creds_provider);
87 }
88
89 config_loader.load().await
90 }
91
92 pub fn count_fields(&self) -> usize {
93 self.profile.is_some() as usize
94 + self.region.is_some() as usize
95 + self.alias.is_some() as usize
96 + self.retry_attempts.is_some() as usize
97 + self.endpoint_url.is_some() as usize
98 }
99
100 pub fn serialize_fields<S>(
101 &self,
102 state: &mut <S as serde::Serializer>::SerializeStruct,
103 ) -> Result<(), S::Error>
104 where
105 S: serde::Serializer,
106 {
107 if let Some(ref profile) = self.profile {
108 state.serialize_field("profile", profile)?;
109 }
110 if let Some(ref region) = self.region {
111 state.serialize_field("region", region)?;
112 }
113 if let Some(ref alias) = self.alias {
114 state.serialize_field("alias", alias)?;
115 }
116 if let Some(ref retry_attempts) = self.retry_attempts {
117 state.serialize_field("retry_attempts", retry_attempts)?;
118 }
119 if let Some(ref endpoint_url) = self.endpoint_url {
120 state.serialize_field("endpoint_url", endpoint_url)?;
121 }
122
123 Ok(())
124 }
125}
126
127pub mod aws_sdk_config {
128 pub use aws_types::SdkConfig;
129}
130pub use aws_sdk_lambda;
131
132#[cfg(test)]
133mod tests {
134 use aws_sdk_lambda::config::{ProvideCredentials, Region};
135
136 use crate::RemoteConfig;
137
138 fn setup() {
139 let manifest_dir = env!("CARGO_MANIFEST_DIR");
140 unsafe {
141 std::env::set_var(
142 "AWS_CONFIG_FILE",
143 format!("{manifest_dir}/test-data/aws_config"),
144 );
145 std::env::set_var(
146 "AWS_SHARED_CREDENTIALS_FILE",
147 format!("{manifest_dir}/test-data/aws_credentials"),
148 );
149 }
150 }
151
152 #[tokio::test]
157 async fn undefined_profile() {
158 setup();
159
160 let args = RemoteConfig {
161 profile: Some("durian".to_owned()),
162 region: None,
163 alias: None,
164 retry_attempts: Some(1),
165 endpoint_url: None,
166 };
167
168 let config = args.sdk_config(None).await;
169 let creds = config
170 .credentials_provider()
171 .unwrap()
172 .provide_credentials()
173 .await;
174
175 assert_eq!(config.region(), None);
176 assert!(creds.is_err());
177 }
178
179 #[tokio::test]
184 async fn undefined_profile_with_creds() {
185 setup();
186
187 let args = RemoteConfig {
188 profile: Some("cherry".to_owned()),
189 region: None,
190 alias: None,
191 retry_attempts: Some(1),
192 endpoint_url: None,
193 };
194
195 let config = args.sdk_config(None).await;
196 let creds = config
197 .credentials_provider()
198 .unwrap()
199 .provide_credentials()
200 .await
201 .unwrap();
202
203 assert_eq!(config.region(), None);
204 assert_eq!(creds.access_key_id(), "CCCCCCCCCCCCCCCCCCCC");
205 }
206
207 #[tokio::test]
212 async fn profile_with_region() {
213 setup();
214
215 let args = RemoteConfig {
216 profile: Some("apple".to_owned()),
217 region: None,
218 alias: None,
219 retry_attempts: Some(1),
220 endpoint_url: None,
221 };
222
223 let config = args.sdk_config(None).await;
224 let creds = config
225 .credentials_provider()
226 .unwrap()
227 .provide_credentials()
228 .await
229 .unwrap();
230
231 assert_eq!(config.region(), Some(&Region::from_static("ca-central-1")));
232 assert_eq!(creds.access_key_id(), "AAAAAAAAAAAAAAAAAAAA");
233 }
234
235 #[tokio::test]
240 async fn profile_without_region() {
241 setup();
242
243 let args = RemoteConfig {
244 profile: Some("banana".to_owned()),
245 region: None,
246 alias: None,
247 retry_attempts: Some(1),
248 endpoint_url: None,
249 };
250
251 let config = args.sdk_config(None).await;
252 let creds = config
253 .credentials_provider()
254 .unwrap()
255 .provide_credentials()
256 .await
257 .unwrap();
258
259 assert_eq!(config.region(), None);
260 assert_eq!(creds.access_key_id(), "BBBBBBBBBBBBBBBBBBBB");
261 }
262
263 #[tokio::test]
268 async fn default_profile() {
269 setup();
270
271 let args = RemoteConfig {
272 profile: None,
273 region: None,
274 alias: None,
275 retry_attempts: Some(1),
276 endpoint_url: None,
277 };
278
279 let config = args.sdk_config(None).await;
280 let creds = config
281 .credentials_provider()
282 .unwrap()
283 .provide_credentials()
284 .await
285 .unwrap();
286
287 assert_eq!(config.region(), Some(&Region::from_static("af-south-1")));
288 assert_eq!(creds.access_key_id(), "DDDDDDDDDDDDDDDDDDDD");
289 }
290}