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