aws_runtime/env_config/
file.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Config structs to programmatically customize the profile files that get loaded
7
8use std::fmt;
9use std::path::PathBuf;
10
11/// Provides the ability to programmatically override the profile files that get loaded by the SDK.
12///
13/// The [`Default`] for `EnvConfigFiles` includes the default SDK config and credential files located in
14/// `~/.aws/config` and `~/.aws/credentials` respectively.
15///
16/// Any number of config and credential files may be added to the `EnvConfigFiles` file set, with the
17/// only requirement being that there is at least one of them. Custom file locations that are added
18/// will produce errors if they don't exist, while the default config/credentials files paths are
19/// allowed to not exist even if they're included.
20///
21/// # Example: Using a custom profile file path
22///
23/// ```no_run,ignore
24/// use aws_runtime::env_config::file::{EnvConfigFiles, SharedConfigFileKind};
25/// use std::sync::Arc;
26///
27/// # async fn example() {
28/// let profile_files = EnvConfigFiles::builder()
29///     .with_file(SharedConfigFileKind::Credentials, "some/path/to/credentials-file")
30///     .build();
31/// let sdk_config = aws_config::from_env()
32///     .profile_files(profile_files)
33///     .load()
34///     .await;
35/// # }
36/// ```
37#[derive(Clone, Debug)]
38pub struct EnvConfigFiles {
39    pub(crate) files: Vec<EnvConfigFile>,
40}
41
42impl EnvConfigFiles {
43    /// Returns a builder to create `EnvConfigFiles`
44    pub fn builder() -> Builder {
45        Builder::new()
46    }
47}
48
49impl Default for EnvConfigFiles {
50    fn default() -> Self {
51        Self {
52            files: vec![
53                EnvConfigFile::Default(EnvConfigFileKind::Config),
54                EnvConfigFile::Default(EnvConfigFileKind::Credentials),
55            ],
56        }
57    }
58}
59
60/// Profile file type (config or credentials)
61#[derive(Copy, Clone, Debug)]
62pub enum EnvConfigFileKind {
63    /// The SDK config file that typically resides in `~/.aws/config`
64    Config,
65    /// The SDK credentials file that typically resides in `~/.aws/credentials`
66    Credentials,
67}
68
69impl EnvConfigFileKind {
70    pub(crate) fn default_path(&self) -> &'static str {
71        match &self {
72            EnvConfigFileKind::Credentials => "~/.aws/credentials",
73            EnvConfigFileKind::Config => "~/.aws/config",
74        }
75    }
76
77    pub(crate) fn override_environment_variable(&self) -> &'static str {
78        match &self {
79            EnvConfigFileKind::Config => "AWS_CONFIG_FILE",
80            EnvConfigFileKind::Credentials => "AWS_SHARED_CREDENTIALS_FILE",
81        }
82    }
83}
84
85/// A single config file within a [`EnvConfigFiles`] file set.
86#[derive(Clone)]
87pub(crate) enum EnvConfigFile {
88    /// One of the default profile files (config or credentials in their default locations)
89    Default(EnvConfigFileKind),
90    /// A profile file at a custom location
91    FilePath {
92        kind: EnvConfigFileKind,
93        path: PathBuf,
94    },
95    /// The direct contents of a profile file
96    FileContents {
97        kind: EnvConfigFileKind,
98        contents: String,
99    },
100}
101
102impl fmt::Debug for EnvConfigFile {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            Self::Default(kind) => f.debug_tuple("Default").field(kind).finish(),
106            Self::FilePath { kind, path } => f
107                .debug_struct("FilePath")
108                .field("kind", kind)
109                .field("path", path)
110                .finish(),
111            // Security: Redact the file contents since they may have credentials in them
112            Self::FileContents { kind, contents: _ } => f
113                .debug_struct("FileContents")
114                .field("kind", kind)
115                .field("contents", &"** redacted **")
116                .finish(),
117        }
118    }
119}
120
121/// Builder for [`EnvConfigFiles`].
122#[derive(Clone, Default, Debug)]
123pub struct Builder {
124    with_config: bool,
125    with_credentials: bool,
126    custom_sources: Vec<EnvConfigFile>,
127}
128
129impl Builder {
130    /// Creates a new builder instance.
131    pub fn new() -> Self {
132        Default::default()
133    }
134
135    /// Include the default SDK config file in the list of profile files to be loaded.
136    ///
137    /// The default SDK config typically resides in `~/.aws/config`. When this flag is enabled,
138    /// this config file will be included in the profile files that get loaded in the built
139    /// [`EnvConfigFiles`] file set.
140    ///
141    /// This flag defaults to `false` when using the builder to construct [`EnvConfigFiles`].
142    pub fn include_default_config_file(mut self, include_default_config_file: bool) -> Self {
143        self.with_config = include_default_config_file;
144        self
145    }
146
147    /// Include the default SDK credentials file in the list of profile files to be loaded.
148    ///
149    /// The default SDK credentials typically reside in `~/.aws/credentials`. When this flag is enabled,
150    /// this credentials file will be included in the profile files that get loaded in the built
151    /// [`EnvConfigFiles`] file set.
152    ///
153    /// This flag defaults to `false` when using the builder to construct [`EnvConfigFiles`].
154    pub fn include_default_credentials_file(
155        mut self,
156        include_default_credentials_file: bool,
157    ) -> Self {
158        self.with_credentials = include_default_credentials_file;
159        self
160    }
161
162    /// Include a custom `file` in the list of profile files to be loaded.
163    ///
164    /// The `kind` informs the parser how to treat the file. If it's intended to be like
165    /// the SDK credentials file typically in `~/.aws/config`, then use [`EnvConfigFileKind::Config`].
166    /// Otherwise, use [`EnvConfigFileKind::Credentials`].
167    pub fn with_file(mut self, kind: EnvConfigFileKind, file: impl Into<PathBuf>) -> Self {
168        self.custom_sources.push(EnvConfigFile::FilePath {
169            kind,
170            path: file.into(),
171        });
172        self
173    }
174
175    /// Include custom file `contents` in the list of profile files to be loaded.
176    ///
177    /// The `kind` informs the parser how to treat the file. If it's intended to be like
178    /// the SDK credentials file typically in `~/.aws/config`, then use [`EnvConfigFileKind::Config`].
179    /// Otherwise, use [`EnvConfigFileKind::Credentials`].
180    pub fn with_contents(mut self, kind: EnvConfigFileKind, contents: impl Into<String>) -> Self {
181        self.custom_sources.push(EnvConfigFile::FileContents {
182            kind,
183            contents: contents.into(),
184        });
185        self
186    }
187
188    /// Build the [`EnvConfigFiles`] file set.
189    pub fn build(self) -> EnvConfigFiles {
190        let mut files = self.custom_sources;
191        if self.with_credentials {
192            files.insert(0, EnvConfigFile::Default(EnvConfigFileKind::Credentials));
193        }
194        if self.with_config {
195            files.insert(0, EnvConfigFile::Default(EnvConfigFileKind::Config));
196        }
197        if files.is_empty() {
198            panic!("At least one profile file must be included in the `EnvConfigFiles` file set.");
199        }
200        EnvConfigFiles { files }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn redact_file_contents_in_profile_file_debug() {
210        let shared_config_file = EnvConfigFile::FileContents {
211            kind: EnvConfigFileKind::Config,
212            contents: "sensitive_contents".into(),
213        };
214        let debug = format!("{shared_config_file:?}");
215        assert!(!debug.contains("sensitive_contents"));
216        assert!(debug.contains("** redacted **"));
217    }
218
219    #[test]
220    fn build_correctly_orders_default_config_credentials() {
221        let shared_config_files = EnvConfigFiles::builder()
222            .with_file(EnvConfigFileKind::Config, "foo")
223            .include_default_credentials_file(true)
224            .include_default_config_file(true)
225            .build();
226        assert_eq!(3, shared_config_files.files.len());
227        assert!(matches!(
228            shared_config_files.files[0],
229            EnvConfigFile::Default(EnvConfigFileKind::Config)
230        ));
231        assert!(matches!(
232            shared_config_files.files[1],
233            EnvConfigFile::Default(EnvConfigFileKind::Credentials)
234        ));
235        assert!(matches!(
236            shared_config_files.files[2],
237            EnvConfigFile::FilePath {
238                kind: EnvConfigFileKind::Config,
239                path: _
240            }
241        ));
242    }
243
244    #[test]
245    #[should_panic]
246    fn empty_builder_panics() {
247        EnvConfigFiles::builder().build();
248    }
249}