aws_config/
provider_config.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Configuration Options for Credential Providers
7
8use crate::env_service_config::EnvServiceConfig;
9use crate::profile;
10#[allow(deprecated)]
11use crate::profile::profile_file::ProfileFiles;
12use crate::profile::{ProfileFileLoadError, ProfileSet};
13use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
14use aws_smithy_async::time::{SharedTimeSource, TimeSource};
15use aws_smithy_runtime_api::client::http::HttpClient;
16use aws_smithy_runtime_api::shared::IntoShared;
17use aws_smithy_types::error::display::DisplayErrorContext;
18use aws_smithy_types::retry::RetryConfig;
19use aws_types::os_shim_internal::{Env, Fs};
20use aws_types::region::Region;
21use aws_types::sdk_config::SharedHttpClient;
22use aws_types::SdkConfig;
23use std::borrow::Cow;
24use std::fmt::{Debug, Formatter};
25use std::sync::Arc;
26use tokio::sync::OnceCell;
27
28/// Configuration options for Credential Providers
29///
30/// Most credential providers builders offer a `configure` method which applies general provider configuration
31/// options.
32///
33/// To use a region from the default region provider chain use [`ProviderConfig::with_default_region`].
34/// Otherwise, use [`ProviderConfig::without_region`]. Note that some credentials providers require a region
35/// to be explicitly set.
36#[derive(Clone)]
37pub struct ProviderConfig {
38    env: Env,
39    fs: Fs,
40    time_source: SharedTimeSource,
41    http_client: Option<SharedHttpClient>,
42    sleep_impl: Option<SharedAsyncSleep>,
43    region: Option<Region>,
44    use_fips: Option<bool>,
45    use_dual_stack: Option<bool>,
46    /// An AWS profile created from `ProfileFiles` and a `profile_name`
47    parsed_profile: Arc<OnceCell<Result<ProfileSet, ProfileFileLoadError>>>,
48    /// A list of [std::path::Path]s to profile files
49    #[allow(deprecated)]
50    profile_files: ProfileFiles,
51    /// An override to use when constructing a `ProfileSet`
52    profile_name_override: Option<Cow<'static, str>>,
53}
54
55impl Debug for ProviderConfig {
56    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct("ProviderConfig")
58            .field("env", &self.env)
59            .field("fs", &self.fs)
60            .field("time_source", &self.time_source)
61            .field("http_client", &self.http_client)
62            .field("sleep_impl", &self.sleep_impl)
63            .field("region", &self.region)
64            .field("use_fips", &self.use_fips)
65            .field("use_dual_stack", &self.use_dual_stack)
66            .field("profile_name_override", &self.profile_name_override)
67            .finish()
68    }
69}
70
71impl Default for ProviderConfig {
72    fn default() -> Self {
73        Self {
74            env: Env::default(),
75            fs: Fs::default(),
76            time_source: SharedTimeSource::default(),
77            http_client: None,
78            sleep_impl: default_async_sleep(),
79            region: None,
80            use_fips: None,
81            use_dual_stack: None,
82            parsed_profile: Default::default(),
83            #[allow(deprecated)]
84            profile_files: ProfileFiles::default(),
85            profile_name_override: None,
86        }
87    }
88}
89
90#[cfg(test)]
91impl ProviderConfig {
92    /// ProviderConfig with all configuration removed
93    ///
94    /// Unlike [`ProviderConfig::empty`] where `env` and `fs` will use their non-mocked implementations,
95    /// this method will use an empty mock environment and an empty mock file system.
96    pub fn no_configuration() -> Self {
97        use aws_smithy_async::time::StaticTimeSource;
98        use std::collections::HashMap;
99        use std::time::UNIX_EPOCH;
100        let fs = Fs::from_raw_map(HashMap::new());
101        let env = Env::from_slice(&[]);
102        Self {
103            parsed_profile: Default::default(),
104            #[allow(deprecated)]
105            profile_files: ProfileFiles::default(),
106            env,
107            fs,
108            time_source: SharedTimeSource::new(StaticTimeSource::new(UNIX_EPOCH)),
109            http_client: None,
110            sleep_impl: None,
111            region: None,
112            use_fips: None,
113            use_dual_stack: None,
114            profile_name_override: None,
115        }
116    }
117}
118
119impl ProviderConfig {
120    /// Create a default provider config with the region unset.
121    ///
122    /// Using this option means that you may need to set a region manually.
123    ///
124    /// This constructor will use a default value for the HTTPS connector and Sleep implementation
125    /// when they are enabled as crate features which is usually the correct option. To construct
126    /// a `ProviderConfig` without these fields set, use [`ProviderConfig::empty`].
127    ///
128    ///
129    /// # Examples
130    /// ```no_run
131    /// # #[cfg(feature = "rustls")]
132    /// # fn example() {
133    /// use aws_config::provider_config::ProviderConfig;
134    /// use aws_sdk_sts::config::Region;
135    /// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
136    /// let conf = ProviderConfig::without_region().with_region(Some(Region::new("us-east-1")));
137    ///
138    /// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
139    /// # }
140    /// ```
141    pub fn without_region() -> Self {
142        Self::default()
143    }
144
145    /// Constructs a ProviderConfig with no fields set
146    pub fn empty() -> Self {
147        ProviderConfig {
148            env: Env::default(),
149            fs: Fs::default(),
150            time_source: SharedTimeSource::default(),
151            http_client: None,
152            sleep_impl: None,
153            region: None,
154            use_fips: None,
155            use_dual_stack: None,
156            parsed_profile: Default::default(),
157            #[allow(deprecated)]
158            profile_files: ProfileFiles::default(),
159            profile_name_override: None,
160        }
161    }
162
163    /// Initializer for ConfigBag to avoid possibly setting incorrect defaults.
164    pub(crate) fn init(
165        time_source: SharedTimeSource,
166        sleep_impl: Option<SharedAsyncSleep>,
167    ) -> Self {
168        Self {
169            parsed_profile: Default::default(),
170            #[allow(deprecated)]
171            profile_files: ProfileFiles::default(),
172            env: Env::default(),
173            fs: Fs::default(),
174            time_source,
175            http_client: None,
176            sleep_impl,
177            region: None,
178            use_fips: None,
179            use_dual_stack: None,
180            profile_name_override: None,
181        }
182    }
183
184    /// Create a default provider config with the region region automatically loaded from the default chain.
185    ///
186    /// # Examples
187    /// ```no_run
188    /// # async fn test() {
189    /// use aws_config::provider_config::ProviderConfig;
190    /// use aws_sdk_sts::config::Region;
191    /// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
192    /// let conf = ProviderConfig::with_default_region().await;
193    /// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
194    /// }
195    /// ```
196    pub async fn with_default_region() -> Self {
197        Self::without_region().load_default_region().await
198    }
199
200    /// Attempt to get a representation of `SdkConfig` from this `ProviderConfig`.
201    ///
202    ///
203    /// **WARN**: Some options (e.g. `service_config`) can only be set if the profile has been
204    /// parsed already (e.g. by calling [`ProviderConfig::profile()`]). This is an
205    /// imperfect mapping and should be used sparingly.
206    pub(crate) fn client_config(&self) -> SdkConfig {
207        let profiles = self.parsed_profile.get().and_then(|v| v.as_ref().ok());
208        let service_config = EnvServiceConfig {
209            env: self.env(),
210            env_config_sections: profiles.cloned().unwrap_or_default(),
211        };
212
213        let mut builder = SdkConfig::builder()
214            .retry_config(RetryConfig::standard())
215            .region(self.region())
216            .time_source(self.time_source())
217            .use_fips(self.use_fips().unwrap_or_default())
218            .use_dual_stack(self.use_dual_stack().unwrap_or_default())
219            .service_config(service_config)
220            .behavior_version(crate::BehaviorVersion::latest());
221        builder.set_http_client(self.http_client.clone());
222        builder.set_sleep_impl(self.sleep_impl.clone());
223        builder.build()
224    }
225
226    // When all crate features are disabled, these accessors are unused
227
228    #[allow(dead_code)]
229    pub(crate) fn env(&self) -> Env {
230        self.env.clone()
231    }
232
233    #[allow(dead_code)]
234    pub(crate) fn fs(&self) -> Fs {
235        self.fs.clone()
236    }
237
238    #[allow(dead_code)]
239    pub(crate) fn time_source(&self) -> SharedTimeSource {
240        self.time_source.clone()
241    }
242
243    #[allow(dead_code)]
244    pub(crate) fn http_client(&self) -> Option<SharedHttpClient> {
245        self.http_client.clone()
246    }
247
248    #[allow(dead_code)]
249    pub(crate) fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
250        self.sleep_impl.clone()
251    }
252
253    #[allow(dead_code)]
254    pub(crate) fn region(&self) -> Option<Region> {
255        self.region.clone()
256    }
257
258    #[allow(dead_code)]
259    pub(crate) fn use_fips(&self) -> Option<bool> {
260        self.use_fips
261    }
262
263    #[allow(dead_code)]
264    pub(crate) fn use_dual_stack(&self) -> Option<bool> {
265        self.use_dual_stack
266    }
267
268    pub(crate) async fn try_profile(&self) -> Result<&ProfileSet, &ProfileFileLoadError> {
269        let parsed_profile = self
270            .parsed_profile
271            .get_or_init(|| async {
272                let profile = profile::load(
273                    &self.fs,
274                    &self.env,
275                    &self.profile_files,
276                    self.profile_name_override.clone(),
277                )
278                .await;
279                if let Err(err) = profile.as_ref() {
280                    tracing::warn!(err = %DisplayErrorContext(&err), "failed to parse profile")
281                }
282                profile
283            })
284            .await;
285        parsed_profile.as_ref()
286    }
287
288    pub(crate) async fn profile(&self) -> Option<&ProfileSet> {
289        self.try_profile().await.ok()
290    }
291
292    /// Override the region for the configuration
293    pub fn with_region(mut self, region: Option<Region>) -> Self {
294        self.region = region;
295        self
296    }
297
298    /// Override the `use_fips` setting.
299    pub(crate) fn with_use_fips(mut self, use_fips: Option<bool>) -> Self {
300        self.use_fips = use_fips;
301        self
302    }
303
304    /// Override the `use_dual_stack` setting.
305    pub(crate) fn with_use_dual_stack(mut self, use_dual_stack: Option<bool>) -> Self {
306        self.use_dual_stack = use_dual_stack;
307        self
308    }
309
310    pub(crate) fn with_profile_name(self, profile_name: String) -> Self {
311        let profile_files = self.profile_files.clone();
312        self.with_profile_config(Some(profile_files), Some(profile_name))
313    }
314
315    /// Override the profile file paths (`~/.aws/config` by default) and name (`default` by default)
316    #[allow(deprecated)]
317    pub(crate) fn with_profile_config(
318        self,
319        profile_files: Option<ProfileFiles>,
320        profile_name_override: Option<String>,
321    ) -> Self {
322        // if there is no override, then don't clear out `parsed_profile`.
323        if profile_files.is_none() && profile_name_override.is_none() {
324            return self;
325        }
326        ProviderConfig {
327            // clear out the profile since we need to reparse it
328            parsed_profile: Default::default(),
329            profile_files: profile_files.unwrap_or(self.profile_files),
330            profile_name_override: profile_name_override
331                .map(Cow::Owned)
332                .or(self.profile_name_override),
333            ..self
334        }
335    }
336
337    /// Use the [default region chain](crate::default_provider::region) to set the
338    /// region for this configuration
339    ///
340    /// Note: the `env` and `fs` already set on this provider will be used when loading the default region.
341    pub async fn load_default_region(self) -> Self {
342        use crate::default_provider::region::DefaultRegionChain;
343        let provider_chain = DefaultRegionChain::builder().configure(&self).build();
344        self.with_region(provider_chain.region().await)
345    }
346
347    pub(crate) fn with_fs(self, fs: Fs) -> Self {
348        ProviderConfig {
349            parsed_profile: Default::default(),
350            fs,
351            ..self
352        }
353    }
354
355    pub(crate) fn with_env(self, env: Env) -> Self {
356        ProviderConfig {
357            parsed_profile: Default::default(),
358            env,
359            ..self
360        }
361    }
362
363    /// Override the time source for this configuration
364    pub fn with_time_source(self, time_source: impl TimeSource + 'static) -> Self {
365        ProviderConfig {
366            time_source: time_source.into_shared(),
367            ..self
368        }
369    }
370
371    /// Override the HTTP client for this configuration
372    pub fn with_http_client(self, http_client: impl HttpClient + 'static) -> Self {
373        ProviderConfig {
374            http_client: Some(http_client.into_shared()),
375            ..self
376        }
377    }
378
379    /// Override the sleep implementation for this configuration
380    pub fn with_sleep_impl(self, sleep_impl: impl AsyncSleep + 'static) -> Self {
381        ProviderConfig {
382            sleep_impl: Some(sleep_impl.into_shared()),
383            ..self
384        }
385    }
386}