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}