@@ -327,7 +327,68 @@ S3Options S3Options::FromAssumeRoleWithWebIdentity() {
327327}
328328
329329Result<S3Options> S3Options::FromUri (const Uri& uri, std::string* out_path) {
330- S3Options options;
330+ return FromUriAndOptions (uri, FileSystemFactoryOptions{}, out_path);
331+ }
332+
333+ Result<S3Options> S3Options::FromUri (const std::string& uri_string,
334+ std::string* out_path) {
335+ Uri uri;
336+ RETURN_NOT_OK (uri.Parse (uri_string));
337+ return FromUri (uri, out_path);
338+ }
339+
340+ namespace {
341+
342+ template <typename T>
343+ Result<T> GetOption (const std::string& key, const std::any& value) {
344+ if (const auto * v = std::any_cast<T>(&value)) {
345+ return *v;
346+ }
347+ return Status::Invalid (" S3 filesystem option '" , key, " ' has the wrong type" );
348+ }
349+
350+ template <typename T>
351+ Result<std::shared_ptr<const T>> GetConstSharedPtrOption (const std::string& key,
352+ const std::any& value) {
353+ if (const auto * v = std::any_cast<std::shared_ptr<const T>>(&value)) return *v;
354+ if (const auto * v = std::any_cast<std::shared_ptr<T>>(&value)) return *v;
355+ return Status::Invalid (" S3 filesystem option '" , key, " ' has the wrong type" );
356+ }
357+
358+ } // namespace
359+
360+ Result<S3Options> S3Options::FromUriAndOptions (const ::arrow::util::Uri& uri,
361+ const FileSystemFactoryOptions& options,
362+ std::string* out_path) {
363+ std::optional<std::string> access_key, secret_key, session_token;
364+ std::shared_ptr<S3RetryStrategy> retry_strategy;
365+ std::shared_ptr<const KeyValueMetadata> default_metadata;
366+ for (const auto & [key, value] : options) {
367+ if (key == " access_key" ) {
368+ ARROW_ASSIGN_OR_RAISE (access_key, GetOption<std::string>(key, value));
369+ } else if (key == " secret_key" ) {
370+ ARROW_ASSIGN_OR_RAISE (secret_key, GetOption<std::string>(key, value));
371+ } else if (key == " session_token" ) {
372+ ARROW_ASSIGN_OR_RAISE (session_token, GetOption<std::string>(key, value));
373+ } else if (key == " retry_strategy" ) {
374+ ARROW_ASSIGN_OR_RAISE (retry_strategy,
375+ GetOption<std::shared_ptr<S3RetryStrategy>>(key, value));
376+ } else if (key == " default_metadata" ) {
377+ ARROW_ASSIGN_OR_RAISE (default_metadata,
378+ GetConstSharedPtrOption<KeyValueMetadata>(key, value));
379+ } else {
380+ return Status::Invalid (" Unexpected option for S3 filesystem: '" , key, " '" );
381+ }
382+ }
383+
384+ if (access_key.has_value () != secret_key.has_value ()) {
385+ return Status::Invalid (
386+ " Both 'access_key' and 'secret_key' must be provided together" );
387+ }
388+ if (session_token.has_value () && !access_key.has_value ()) {
389+ return Status::Invalid (" 'session_token' requires 'access_key' and 'secret_key'" );
390+ }
391+ S3Options s3_options;
331392
332393 const auto bucket = uri.host ();
333394 auto path = uri.path ();
@@ -355,68 +416,81 @@ Result<S3Options> S3Options::FromUri(const Uri& uri, std::string* out_path) {
355416 options_map.emplace (kv.first , kv.second );
356417 }
357418
358- const auto username = uri.username ();
359- if (!username.empty ()) {
360- options.ConfigureAccessKey (username, uri.password ());
419+ if (access_key.has_value ()) {
420+ s3_options.ConfigureAccessKey (*access_key, *secret_key, session_token.value_or (" " ));
361421 } else {
362- options.ConfigureDefaultCredentials ();
422+ const auto username = uri.username ();
423+ if (!username.empty ()) {
424+ s3_options.ConfigureAccessKey (username, uri.password ());
425+ } else {
426+ s3_options.ConfigureDefaultCredentials ();
427+ }
363428 }
429+
364430 // Prefer AWS service-specific endpoint url
365431 auto s3_endpoint_env = arrow::internal::GetEnvVar (kAwsEndpointUrlS3EnvVar );
366432 if (s3_endpoint_env.ok ()) {
367- options .endpoint_override = *s3_endpoint_env;
433+ s3_options .endpoint_override = *s3_endpoint_env;
368434 } else {
369435 auto endpoint_env = arrow::internal::GetEnvVar (kAwsEndpointUrlEnvVar );
370436 if (endpoint_env.ok ()) {
371- options .endpoint_override = *endpoint_env;
437+ s3_options .endpoint_override = *endpoint_env;
372438 }
373439 }
374440
375441 bool region_set = false ;
376442 for (const auto & kv : options_map) {
377443 if (kv.first == " region" ) {
378- options .region = kv.second ;
444+ s3_options .region = kv.second ;
379445 region_set = true ;
380446 } else if (kv.first == " scheme" ) {
381- options .scheme = kv.second ;
447+ s3_options .scheme = kv.second ;
382448 } else if (kv.first == " endpoint_override" ) {
383- options .endpoint_override = kv.second ;
449+ s3_options .endpoint_override = kv.second ;
384450 } else if (kv.first == " allow_delayed_open" ) {
385- ARROW_ASSIGN_OR_RAISE (options .allow_delayed_open ,
451+ ARROW_ASSIGN_OR_RAISE (s3_options .allow_delayed_open ,
386452 ::arrow::internal::ParseBoolean (kv.second));
387453 } else if (kv.first == " allow_bucket_creation" ) {
388- ARROW_ASSIGN_OR_RAISE (options .allow_bucket_creation ,
454+ ARROW_ASSIGN_OR_RAISE (s3_options .allow_bucket_creation ,
389455 ::arrow::internal::ParseBoolean (kv.second));
390456 } else if (kv.first == " allow_bucket_deletion" ) {
391- ARROW_ASSIGN_OR_RAISE (options .allow_bucket_deletion ,
457+ ARROW_ASSIGN_OR_RAISE (s3_options .allow_bucket_deletion ,
392458 ::arrow::internal::ParseBoolean (kv.second));
393459 } else if (kv.first == " tls_ca_file_path" ) {
394- options .tls_ca_file_path = kv.second ;
460+ s3_options .tls_ca_file_path = kv.second ;
395461 } else if (kv.first == " tls_ca_dir_path" ) {
396- options .tls_ca_dir_path = kv.second ;
462+ s3_options .tls_ca_dir_path = kv.second ;
397463 } else if (kv.first == " tls_verify_certificates" ) {
398- ARROW_ASSIGN_OR_RAISE (options .tls_verify_certificates ,
464+ ARROW_ASSIGN_OR_RAISE (s3_options .tls_verify_certificates ,
399465 ::arrow::internal::ParseBoolean (kv.second));
400466 } else if (kv.first == " smart_defaults" ) {
401- options .smart_defaults = kv.second ;
467+ s3_options .smart_defaults = kv.second ;
402468 } else {
403469 return Status::Invalid (" Unexpected query parameter in S3 URI: '" , kv.first , " '" );
404470 }
405471 }
406472
407- if (!region_set && !bucket.empty () && options.endpoint_override .empty ()) {
473+ if (retry_strategy) {
474+ s3_options.retry_strategy = std::move (retry_strategy);
475+ }
476+ if (default_metadata) {
477+ s3_options.default_metadata = std::move (default_metadata);
478+ }
479+
480+ if (!region_set && !bucket.empty () && s3_options.endpoint_override .empty ()) {
408481 // XXX Should we use a dedicated resolver with the given credentials?
409- ARROW_ASSIGN_OR_RAISE (options .region , ResolveS3BucketRegion (bucket));
482+ ARROW_ASSIGN_OR_RAISE (s3_options .region , ResolveS3BucketRegion (bucket));
410483 }
411484
412- return options ;
485+ return s3_options ;
413486}
414487
415- Result<S3Options> S3Options::FromUri (const std::string& uri_string,
416- std::string* out_path) {
488+ Result<S3Options> S3Options::FromUriAndOptions (const std::string& uri_string,
489+ const FileSystemFactoryOptions& options,
490+ std::string* out_path) {
417491 Uri uri;
418492 RETURN_NOT_OK (uri.Parse (uri_string));
419- return FromUri (uri, out_path);
493+ return FromUriAndOptions (uri, options , out_path);
420494}
421495
422496bool S3Options::Equals (const S3Options& other) const {
@@ -3606,13 +3680,9 @@ auto kS3FileSystemModule = ARROW_REGISTER_FILESYSTEM(
36063680 [](const arrow::util::Uri& uri, const FileSystemFactoryOptions& options,
36073681 const io::IOContext& io_context,
36083682 std::string* out_path) -> Result<std::shared_ptr<fs::FileSystem>> {
3609- if (!options.empty ()) {
3610- return Status::NotImplemented (
3611- " S3 filesystem factory options are not supported yet, got: " , options.size (),
3612- " option(s)" );
3613- }
36143683 RETURN_NOT_OK (EnsureS3Initialized ());
3615- ARROW_ASSIGN_OR_RAISE (auto s3_options, S3Options::FromUri (uri, out_path));
3684+ ARROW_ASSIGN_OR_RAISE (auto s3_options,
3685+ S3Options::FromUriAndOptions (uri, options, out_path));
36163686 return S3FileSystem::Make (s3_options, io_context);
36173687 },
36183688 [] { DCHECK_OK (EnsureS3Finalized ()); });
0 commit comments