Skip to content

Commit 09b13aa

Browse files
committed
Allow reading S3 profile credentials from file
Per KT request, also allow S3 profile credentials to be read from AWS credentials files using boto3's built-in lookup logic.
1 parent 82b9fb9 commit 09b13aa

File tree

2 files changed

+30
-25
lines changed

2 files changed

+30
-25
lines changed

python/lsst/resources/s3utils.py

+29-24
Original file line numberDiff line numberDiff line change
@@ -195,19 +195,20 @@ def getS3Client(profile: str | None = None) -> boto3.client:
195195
196196
Notes
197197
-----
198-
If an explicit profile name is specified, its configuration is read from an
199-
environment variable named ``LSST_RESOURCES_S3_PROFILE_<profile>``. Note
200-
that the name of the profile is case sensitive. This configuration is
201-
specified in the format:
202-
``https://<access key ID>:<secret key>@<s3 endpoint hostname>``
198+
If an explicit profile name is specified, its configuration will be read
199+
from an environment variable named ``LSST_RESOURCES_S3_PROFILE_<profile>``
200+
if it exists. Note that the name of the profile is case sensitive. This
201+
configuration is specified in the format: ``https://<access key ID>:<secret
202+
key>@<s3 endpoint hostname>``. If the access key ID or secret key values
203+
contain slashes, the slashes must be URI-encoded (replace "/" with "%2F").
203204
204-
If the access key ID or secret key values contain slashes, the slashes must
205-
be URI-encoded (replace "/" with "%2F".) The access key ID and secret key
206-
are optional -- if not specified, they will be looked up via the AWS
207-
credentials file.
205+
If profile is `None` or the profile environment variable was not set, the
206+
configuration is read from the environment variable ``S3_ENDPOINT_URL``.
207+
If it is not specified, the default AWS endpoint is used.
208208
209-
If profile is `None`, this configuration is from the environment variable
210-
S3_ENDPOINT_URL. If none is specified, the default AWS one is used.
209+
The access key ID and secret key are optional -- if not specified, they
210+
will be looked up via the `AWS credentials file
211+
<https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html>`_.
211212
212213
If the environment variable LSST_DISABLE_BUCKET_VALIDATION exists
213214
and has a value that is not empty, "0", "f", "n", or "false"
@@ -220,33 +221,37 @@ def getS3Client(profile: str | None = None) -> boto3.client:
220221
if botocore is None:
221222
raise ModuleNotFoundError("Could not find botocore. Are you sure it is installed?")
222223

223-
if profile is None:
224-
endpoint = os.environ.get("S3_ENDPOINT_URL", None)
225-
if not endpoint:
226-
endpoint = None # Handle ""
227-
else:
224+
endpoint = None
225+
if profile is not None:
228226
var_name = f"LSST_RESOURCES_S3_PROFILE_{profile}"
229227
endpoint = os.environ.get(var_name, None)
230-
if not endpoint:
231-
raise RuntimeError(
232-
f"No configuration found for requested S3 profile '{profile}'."
233-
f" Set the environment variable '{var_name}' to configure it."
234-
)
228+
if not endpoint:
229+
endpoint = os.environ.get("S3_ENDPOINT_URL", None)
230+
if not endpoint:
231+
endpoint = None # Handle ""
235232

236233
disable_value = os.environ.get("LSST_DISABLE_BUCKET_VALIDATION", "0")
237234
skip_validation = not re.search(r"^(0|f|n|false)?$", disable_value, re.I)
238235

239-
return _get_s3_client(endpoint, skip_validation)
236+
return _get_s3_client(endpoint, profile, skip_validation)
240237

241238

242239
@functools.lru_cache
243-
def _get_s3_client(endpoint: str | None, skip_validation: bool) -> boto3.client:
240+
def _get_s3_client(endpoint: str | None, profile: str | None, skip_validation: bool) -> boto3.client:
244241
# Helper function to cache the client for this endpoint
245242
config = botocore.config.Config(read_timeout=180, retries={"mode": "adaptive", "max_attempts": 10})
246243

247244
endpoint_config = _parse_endpoint_config(endpoint)
248245

249-
client = boto3.client(
246+
if endpoint_config.access_key_id is not None and endpoint_config.secret_access_key is not None:
247+
# We already have the necessary configuration for the profile, so do
248+
# not pass the profile to boto3. boto3 will raise an exception if the
249+
# profile is not defined in its configuration file, whether or not it
250+
# needs to read the configuration from it.
251+
profile = None
252+
session = boto3.Session(profile_name=profile)
253+
254+
client = session.client(
250255
"s3",
251256
endpoint_url=endpoint_config.endpoint_url,
252257
aws_access_key_id=endpoint_config.access_key_id,

tests/test_s3.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def setUp(self):
253253
super().setUp()
254254

255255
def test_missing_profile(self):
256-
with self.assertRaisesRegex(RuntimeError, "No configuration found for requested S3 profile"):
256+
with self.assertRaises(botocore.exceptions.ProfileNotFound):
257257
ResourcePath("s3://otherprofile@bucket").read()
258258

259259
def test_s3_endpoint_url(self):

0 commit comments

Comments
 (0)