11//! This module allows application of settings from URIs or stdin. The inputs are expected to be
22//! TOML settings files, in the same format as user data, or the JSON equivalent. The inputs are
33//! pulled and applied to the API server in a single transaction.
4- //! use aws_smithy_runtime_api::client::result::SdkError;
5- use aws_sdk_secretsmanager:: operation:: get_secret_value:: GetSecretValueError ;
6- use aws_sdk_secretsmanager:: config:: http:: HttpResponse as SdkHttpResponse ;
7- use aws_sdk_ssm:: operation:: get_parameter:: GetParameterError ;
8- use crate :: uri_resolver:: { StdinUri , FileUri , HttpUri , S3Uri , UriResolver , SecretsManagerUri , SsmUri } ;
4+ use crate :: apply:: error:: ResolverFailureSnafu ;
95use crate :: rando;
10- use futures:: future:: { join, ready, TryFutureExt } ;
6+ use futures:: future:: { join, ready} ;
117use futures:: stream:: { self , StreamExt } ;
128use reqwest:: Url ;
139use serde:: de:: { Deserialize , IntoDeserializer } ;
14- use snafu:: { futures :: try_future :: TryFutureExt as SnafuTryFutureExt , OptionExt , ResultExt } ;
10+ use snafu:: { OptionExt , ResultExt } ;
1511use std:: convert:: TryFrom ;
1612use std:: path:: Path ;
17- use tokio:: io:: AsyncReadExt ;
18-
1913
2014/// Reads settings in TOML or JSON format from files at the requested URIs (or from stdin, if given
2115/// "-"), then commits them in a single transaction and applies them to the system.
@@ -72,49 +66,65 @@ where
7266 Ok ( ( ) )
7367}
7468
69+ /// Holds the raw input string and (if it parses) the URL.
70+ pub struct SettingsInput {
71+ pub input : String ,
72+ pub parsed_url : Option < Url > ,
73+ }
74+ impl SettingsInput {
75+ pub ( crate ) fn new ( input : impl Into < String > ) -> Self {
76+ let input = input. into ( ) ;
77+ let parsed_url = Url :: parse ( & input) . ok ( ) ;
78+ SettingsInput { input, parsed_url }
79+ }
80+ }
81+
7582/// Retrieves the given source location and returns the result in a String.
76- pub async fn get ( input : & str ) -> Result < String > {
77- let resolver = select_resolver ( input) ?;
78- resolver. resolve ( ) . await
83+ async fn get < S > ( input_source : S ) -> Result < String >
84+ where
85+ S : AsRef < str > ,
86+ {
87+ let settings = SettingsInput :: new ( input_source. as_ref ( ) ) ;
88+ let resolver = select_resolver ( & settings) ?;
89+ resolver. resolve ( ) . await . context ( ResolverFailureSnafu )
7990}
8091
81- /// Choose which UriResolver applies to `input` (stdin, file://, http(s):// or s3://).
82- fn select_resolver ( input : & str ) -> Result < Box < dyn UriResolver > > {
83- // 1) "-" → stdin
84- if let Ok ( r) = StdinUri :: try_from ( input) {
92+ /// Choose which UriResolver applies to `input` (stdin, file://, http(s)://, s3://, secretsmanager://, and ssm://).
93+ fn select_resolver ( input : & SettingsInput ) -> Result < Box < dyn crate :: uri_resolver:: UriResolver > > {
94+ use crate :: uri_resolver;
95+
96+ // stdin ("-")
97+ if let Ok ( r) = uri_resolver:: StdinUri :: try_from ( input) {
8598 return Ok ( Box :: new ( r) ) ;
8699 }
87100
88- // 2) parse as a URL
89- let url = Url :: parse ( input) . context ( error:: UriSnafu { input_source : input. to_string ( ) } ) ?;
90-
91- // 3) file://
92- if let Ok ( r) = FileUri :: try_from ( & url) {
101+ // file://
102+ if let Ok ( r) = uri_resolver:: FileUri :: try_from ( input) {
93103 return Ok ( Box :: new ( r) ) ;
94104 }
95105
96- // 4) http(s)://
97- if let Ok ( r) = HttpUri :: try_from ( url . clone ( ) ) {
106+ // http(s)://
107+ if let Ok ( r) = uri_resolver :: HttpUri :: try_from ( input ) {
98108 return Ok ( Box :: new ( r) ) ;
99109 }
100110
101- // 5) s3://
102- if let Ok ( r) = S3Uri :: try_from ( url . clone ( ) ) {
111+ // s3://
112+ if let Ok ( r) = uri_resolver :: S3Uri :: try_from ( input ) {
103113 return Ok ( Box :: new ( r) ) ;
104114 }
105115
106- // 6) secretsmanager://
107- if let Ok ( r) = SecretsManagerUri :: try_from ( input) {
116+ // secretsmanager://
117+ if let Ok ( r) = uri_resolver :: SecretsManagerUri :: try_from ( input) {
108118 return Ok ( Box :: new ( r) ) ;
109119 }
110120
111- // 6) ssm://
112- if let Ok ( r) = SsmUri :: try_from ( input) {
121+ // ssm://
122+ if let Ok ( r) = uri_resolver :: SsmUri :: try_from ( input) {
113123 return Ok ( Box :: new ( r) ) ;
114124 }
115125
116126 error:: NoResolverSnafu {
117- input_source : input. to_string ( ) ,
127+ input_source : input. input . clone ( ) ,
118128 }
119129 . fail ( )
120130}
@@ -153,8 +163,6 @@ fn format_change(input: &str, input_source: &str) -> Result<String> {
153163}
154164
155165pub ( crate ) mod error {
156- use aws_sdk_secretsmanager:: operation:: get_secret_value:: GetSecretValueError ;
157- use aws_sdk_ssm:: operation:: get_parameter:: GetParameterError ;
158166 use snafu:: Snafu ;
159167
160168 #[ derive( Debug , Snafu ) ]
@@ -168,15 +176,6 @@ pub(crate) mod error {
168176 source : Box < crate :: Error > ,
169177 } ,
170178
171- #[ snafu( display( "Failed to read given file '{}': {}" , input_source, source) ) ]
172- FileRead {
173- input_source : String ,
174- source : std:: io:: Error ,
175- } ,
176-
177- #[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
178- FileUri { input_source : String } ,
179-
180179 #[ snafu( display( "No URI resolver found for '{}'" , input_source) ) ]
181180 NoResolver { input_source : String } ,
182181
@@ -234,45 +233,9 @@ pub(crate) mod error {
234233 source : reqwest:: Error ,
235234 } ,
236235
237- #[ snafu( display( "Invalid S3 URI '{}': missing bucket name" , input_source) ) ]
238- S3UriMissingBucket { input_source : String } ,
239-
240236 #[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
241237 InvalidFileUri { input_source : String } ,
242238
243- #[ snafu( display( "Given HTTP(S) URI '{}'" , input_source) ) ]
244- InvalidHTTPUri { input_source : String } ,
245-
246- #[ snafu( display( "Failed to read standard input: {}" , source) ) ]
247- StdinRead { source : std:: io:: Error } ,
248-
249- #[ snafu( display( "Invalid S3 URI scheme for '{}', expected s3://" , input_source) ) ]
250- S3UriScheme { input_source : String } ,
251-
252- #[ snafu( display( "Invalid Secrets Manager URI scheme for '{}', expected secretsmanager://" , input_source) ) ]
253- SecretsManagerUri { input_source : String } ,
254-
255- #[ snafu( display( "Invalid SSM URI scheme for '{}', expected ssm://" , input_source) ) ]
256- SsmUri { input_source : String } ,
257-
258- #[ snafu( display( "Failed to fetch secret '{}' from Secrets Manager: {}" , secret_id, source) ) ]
259- SecretsManagerGet {
260- secret_id : String ,
261- source : aws_sdk_secretsmanager:: error:: SdkError < GetSecretValueError > ,
262- } ,
263-
264- #[ snafu( display( "Secrets Manager secret '{}' did not return a string value" , secret_id) ) ]
265- SecretsManagerStringMissing { secret_id : String } ,
266-
267- #[ snafu( display( "Failed to fetch parameter '{}' from SSM: {}" , parameter_name, source) ) ]
268- SsmGetParameter {
269- parameter_name : String ,
270- source : aws_sdk_ssm:: error:: SdkError < GetParameterError > ,
271- } ,
272-
273- #[ snafu( display( "SSM parameter '{}' did not return a string value" , parameter_name) ) ]
274- SsmParameterMissing { parameter_name : String } ,
275-
276239 #[ snafu( display(
277240 "Failed to translate TOML from '{}' to JSON for API: {}" ,
278241 input_source,
@@ -288,7 +251,13 @@ pub(crate) mod error {
288251 input_source : String ,
289252 source : url:: ParseError ,
290253 } ,
254+
255+ #[ snafu( display( "Resolver failed: {}" , source) ) ]
256+ ResolverFailure { source : crate :: uri_resolver:: ResolverError } ,
257+
291258 }
259+
292260}
293261pub use error:: Error ;
294262pub type Result < T > = std:: result:: Result < T , error:: Error > ;
263+
0 commit comments