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