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
13pub const 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_iam;
131pub use aws_sdk_lambda;
132
133#[cfg(test)]
134mod tests {
135 use aws_sdk_lambda::config::{ProvideCredentials, Region};
136
137 use crate::RemoteConfig;
138
139 fn setup() {
140 let manifest_dir = env!("CARGO_MANIFEST_DIR");
141 unsafe {
142 std::env::set_var(
143 "AWS_CONFIG_FILE",
144 format!("{manifest_dir}/test-data/aws_config"),
145 );
146 std::env::set_var(
147 "AWS_SHARED_CREDENTIALS_FILE",
148 format!("{manifest_dir}/test-data/aws_credentials"),
149 );
150 }
151 }
152
153 #[tokio::test]
158 async fn undefined_profile() {
159 setup();
160
161 let args = RemoteConfig {
162 profile: Some("durian".to_owned()),
163 region: None,
164 alias: None,
165 retry_attempts: Some(1),
166 endpoint_url: None,
167 };
168
169 let config = args.sdk_config(None).await;
170 let creds = config
171 .credentials_provider()
172 .unwrap()
173 .provide_credentials()
174 .await;
175
176 assert_eq!(config.region(), None);
177 assert!(creds.is_err());
178 }
179
180 #[tokio::test]
185 async fn undefined_profile_with_creds() {
186 setup();
187
188 let args = RemoteConfig {
189 profile: Some("cherry".to_owned()),
190 region: None,
191 alias: None,
192 retry_attempts: Some(1),
193 endpoint_url: None,
194 };
195
196 let config = args.sdk_config(None).await;
197 let creds = config
198 .credentials_provider()
199 .unwrap()
200 .provide_credentials()
201 .await
202 .unwrap();
203
204 assert_eq!(config.region(), None);
205 assert_eq!(creds.access_key_id(), "CCCCCCCCCCCCCCCCCCCC");
206 }
207
208 #[tokio::test]
213 async fn profile_with_region() {
214 setup();
215
216 let args = RemoteConfig {
217 profile: Some("apple".to_owned()),
218 region: None,
219 alias: None,
220 retry_attempts: Some(1),
221 endpoint_url: None,
222 };
223
224 let config = args.sdk_config(None).await;
225 let creds = config
226 .credentials_provider()
227 .unwrap()
228 .provide_credentials()
229 .await
230 .unwrap();
231
232 assert_eq!(config.region(), Some(&Region::from_static("ca-central-1")));
233 assert_eq!(creds.access_key_id(), "AAAAAAAAAAAAAAAAAAAA");
234 }
235
236 #[tokio::test]
241 async fn profile_without_region() {
242 setup();
243
244 let args = RemoteConfig {
245 profile: Some("banana".to_owned()),
246 region: None,
247 alias: None,
248 retry_attempts: Some(1),
249 endpoint_url: None,
250 };
251
252 let config = args.sdk_config(None).await;
253 let creds = config
254 .credentials_provider()
255 .unwrap()
256 .provide_credentials()
257 .await
258 .unwrap();
259
260 assert_eq!(config.region(), None);
261 assert_eq!(creds.access_key_id(), "BBBBBBBBBBBBBBBBBBBB");
262 }
263
264 #[tokio::test]
269 async fn default_profile() {
270 setup();
271
272 let args = RemoteConfig {
273 profile: None,
274 region: None,
275 alias: None,
276 retry_attempts: Some(1),
277 endpoint_url: None,
278 };
279
280 let config = args.sdk_config(None).await;
281 let creds = config
282 .credentials_provider()
283 .unwrap()
284 .provide_credentials()
285 .await
286 .unwrap();
287
288 assert_eq!(config.region(), Some(&Region::from_static("af-south-1")));
289 assert_eq!(creds.access_key_id(), "DDDDDDDDDDDDDDDDDDDD");
290 }
291}