Skip to content

Commit 05a81dc

Browse files
Merge pull request #17 from jkeifer/null-bucket-location
Handle us-east-1 null bucket location
2 parents a868c35 + 9e41185 commit 05a81dc

File tree

6 files changed

+135
-48
lines changed

6 files changed

+135
-48
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ["3.6", "3.7", "3.8"]
15+
python-version: ["3.7", "3.8", "3.9", "3.10"]
1616
steps:
1717
- uses: actions/checkout@v2
1818

@@ -36,4 +36,4 @@ jobs:
3636
with:
3737
token: ${{ secrets.CODECOV_TOKEN }}
3838
file: ./coverage.xml
39-
fail_ci_if_error: false
39+
fail_ci_if_error: false

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9-
## [v0.3.3] - 2022-05-18
9+
## [v0.3.3] - 2022-09-14
1010

1111
### Added
1212
- s3.latest_inventory() takes manifest_age_days argument for how far back to look for manifest
1313
- s3.latest_inventory() takes is_latest argument for filtering on versioned files
1414
- s3.latest_inventory() takes key_contains array argument for filtering on key containing strings of the array
1515

16+
### Changed
17+
- No longer testing against python 3.6
18+
- Now testing against python 3.9 and 3.10
19+
20+
### Fixed
21+
- s3.delete works
22+
- Handle bucket locations in US Standard region
23+
1624
## [v0.3.2] - 2021-07-15
1725

1826
### Added

boto3utils/s3.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ def exists(self, url):
7676
raise
7777
return False
7878

79+
def get_bucket_region(self, bucket_name):
80+
region = self.s3.get_bucket_location(
81+
Bucket=bucket_name)['LocationConstraint']
82+
# US Standard region buckets will have a null location
83+
# https://github.com/aws/aws-cli/issues/3864
84+
return region if region else 'us-east-1'
85+
7986
def upload(self, filename, url, public=False, extra={}, http_url=False):
8087
""" Upload object to S3 uri (bucket + prefix), keeping same base filename """
8188
logger.debug("Uploading %s to %s" % (filename, url))
@@ -89,9 +96,8 @@ def upload(self, filename, url, public=False, extra={}, http_url=False):
8996
parts['key'],
9097
ExtraArgs=extra)
9198
if http_url:
92-
region = self.s3.get_bucket_location(
93-
Bucket=parts['bucket'])['LocationConstraint']
94-
return self.s3_to_https(url_out, region)
99+
return self.s3_to_https(url_out,
100+
self.get_bucket_region(parts['bucket']))
95101
else:
96102
return url_out
97103

@@ -156,8 +162,8 @@ def read_json(self, url):
156162
def delete(self, url):
157163
""" Remove object from S3 """
158164
parts = self.urlparse(url)
159-
response = self.s3.delete_object(Bucket=parts['Bucket'],
160-
Key=parts['Key'])
165+
response = self.s3.delete_object(Bucket=parts['bucket'],
166+
Key=parts['key'])
161167
return response
162168

163169
# function derived from https://alexwlchan.net/2018/01/listing-s3-keys-redux/
@@ -234,7 +240,8 @@ def fenddate(info):
234240
return True if dt < end_date else False
235241

236242
def islatest(info):
237-
if latest := info.get("IsLatest"):
243+
latest = info.get("IsLatest")
244+
if latest:
238245
return latest not in ("false", False)
239246
return True
240247

@@ -308,7 +315,8 @@ def latest_inventory(self, url, **kwargs):
308315
str(key).strip() for key in manifest['fileSchema'].split(',')
309316
]
310317

311-
for i, url in enumerate(self.latest_inventory_files(url, manifest)):
318+
for i, url in enumerate(self.latest_inventory_files(url,
319+
manifest)):
312320
logger.info('Reading inventory file %s' % (i + 1))
313321
results = self.read_inventory_file(url, keys, **kwargs)
314322
yield from results

tests/conftest.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
3+
import moto
4+
import boto3
5+
import pytest
6+
7+
if 'AWS_DEFAULT_REGION' not in os.environ:
8+
os.environ['AWS_DEFAULT_REGION'] = 'us-east-1'
9+
10+
11+
@pytest.fixture
12+
def aws_credentials():
13+
"""Mocked AWS Credentials for moto."""
14+
os.environ['AWS_ACCESS_KEY_ID'] = 'testing'
15+
os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing'
16+
os.environ['AWS_SECURITY_TOKEN'] = 'testing'
17+
os.environ['AWS_SESSION_TOKEN'] = 'testing'
18+
os.environ['AWS_DEFAULT_REGION'] = 'us-east-1'
19+
os.environ['AWS_REGION'] = 'us-east-1'
20+
21+
22+
@pytest.fixture
23+
def s3(aws_credentials):
24+
with moto.mock_s3():
25+
yield boto3.client('s3', region_name='us-east-1')
26+
27+
28+
@pytest.fixture
29+
def s3_west(aws_credentials):
30+
with moto.mock_s3():
31+
yield boto3.client('s3', region_name='us-west-2')
32+
33+
34+
@pytest.fixture
35+
def secretsmanager(aws_credentials):
36+
with moto.mock_secretsmanager():
37+
yield boto3.client('secretsmanager', region_name='us-east-1')

tests/test_s3.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# this must be imported before any boto3 module
2-
from moto import mock_s3
3-
41
import boto3
52
import os
63
import pytest
@@ -9,22 +6,37 @@
96
from shutil import rmtree
107

118
BUCKET = 'testbucket'
9+
BUCKET_WEST = 'testbucket_west'
1210
KEY = 'testkey'
1311

1412

15-
@pytest.fixture(scope='function')
16-
def s3mock():
17-
with mock_s3():
18-
client = boto3.client('s3',
19-
region_name='us-east-1',
20-
aws_access_key_id='noid',
21-
aws_secret_access_key='nokey')
22-
client.create_bucket(Bucket=BUCKET)
23-
client.put_object(Body='helloworld', Bucket=BUCKET, Key=KEY)
24-
client.upload_file(Filename=os.path.join(testpath, 'test.json'),
25-
Bucket=BUCKET,
26-
Key='test.json')
27-
yield client
13+
def create_test_bucket(s3, bucket):
14+
params = {
15+
'Bucket': bucket,
16+
}
17+
18+
if s3.meta.region_name != 'us-east-1':
19+
params['CreateBucketConfiguration'] = {
20+
'LocationConstraint': s3.meta.region_name,
21+
}
22+
23+
s3.create_bucket(**params)
24+
s3.put_object(Body='helloworld', Bucket=bucket, Key=KEY)
25+
s3.upload_file(Filename=os.path.join(testpath, 'test.json'),
26+
Bucket=bucket,
27+
Key='test.json')
28+
29+
30+
@pytest.fixture
31+
def s3mock(s3):
32+
create_test_bucket(s3, BUCKET)
33+
yield s3
34+
35+
36+
@pytest.fixture
37+
def s3mock_west(s3_west):
38+
create_test_bucket(s3_west, BUCKET_WEST)
39+
yield s3_west
2840

2941

3042
testpath = os.path.dirname(__file__)
@@ -55,6 +67,16 @@ def test_s3_to_https():
5567
assert (url == 'https://bucket.s3.us-west-2.amazonaws.com/prefix/filename')
5668

5769

70+
def test_get_bucket_region_null(s3mock):
71+
region = s3().get_bucket_region(BUCKET)
72+
assert region == 'us-east-1'
73+
74+
75+
def test_get_bucket_region(s3mock_west):
76+
region = s3().get_bucket_region(BUCKET_WEST)
77+
assert region == 'us-west-2'
78+
79+
5880
def test_exists(s3mock):
5981
exists = s3().exists('s3://%s/%s' % (BUCKET, 'keymaster'))
6082
assert (exists is False)
@@ -85,6 +107,12 @@ def test_read_json(s3mock):
85107
assert (out['field'] == 'value')
86108

87109

110+
def test_delete(s3mock):
111+
url = 's3://%s/test.json' % BUCKET
112+
out = s3().delete(url)
113+
assert (out['ResponseMetadata']['HTTPStatusCode'] == 204)
114+
115+
88116
def test_find(s3mock):
89117
url = 's3://%s/test' % BUCKET
90118
urls = list(s3().find(url))
@@ -93,10 +121,17 @@ def test_find(s3mock):
93121

94122

95123
def test_latest_inventory():
124+
from botocore.handlers import disable_signing
125+
96126
url = 's3://sentinel-inventory/sentinel-s1-l1c/sentinel-s1-l1c-inventory'
97127
suffix = 'productInfo.json'
98128
session = boto3.Session()
99129
_s3 = s3(session)
130+
131+
# as we are actually hitting S3 (which is not great), we need
132+
# to prevent signing our request with the dummy test credentials
133+
_s3.s3.meta.events.register('choose-signer.s3.*', disable_signing)
134+
100135
for url in _s3.latest_inventory(url, suffix=suffix):
101136
# dt = datetime.strptime(f['LastModifiedDate'], "%Y-%m-%dT%H:%M:%S.%fZ")
102137
# hours = (datetime.today() - dt).seconds // 3600

tests/test_secrets.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,44 @@
1-
# this must be imported before any boto3 module
2-
from moto import mock_secretsmanager
3-
4-
import os
5-
import boto3
61
import pytest
72
import json
83
import base64
94

105
from boto3utils import secrets
116
from botocore.exceptions import ClientError
127

13-
testpath = os.path.dirname(__file__)
14-
158
SECRET_NAME = 'secret'
169
SECRET = {'mock_key': 'mock_val'}
1710
SECRET_STRING = json.dumps(SECRET)
1811
SECRET_BINARY = base64.b64encode(SECRET_STRING.encode())
1912

2013

21-
@mock_secretsmanager
22-
def test_get_secret_string():
23-
boto3.session.Session().client('secretsmanager',
24-
region_name='us-west-2').create_secret(
25-
Name=SECRET_NAME,
26-
SecretString=SECRET_STRING)
14+
@pytest.fixture
15+
def secret(secretsmanager):
16+
secretsmanager.create_secret(
17+
Name=SECRET_NAME,
18+
SecretString=SECRET_STRING,
19+
)
20+
return secretsmanager
21+
22+
23+
@pytest.fixture
24+
def binary_secret(secretsmanager):
25+
secretsmanager.create_secret(
26+
Name=SECRET_NAME,
27+
SecretBinary=SECRET_BINARY,
28+
)
29+
return secretsmanager
30+
31+
32+
def test_get_secret_string(secret):
2733
secret = secrets.get_secret(SECRET_NAME)
2834
assert (secret == SECRET)
2935

3036

31-
@mock_secretsmanager
32-
def test_get_secret_undef():
37+
def test_get_secret_undef(secretsmanager):
3338
with pytest.raises(ClientError):
3439
secrets.get_secret(SECRET_NAME)
3540

3641

37-
@mock_secretsmanager
38-
def test_get_secret_binary():
39-
boto3.session.Session().client('secretsmanager',
40-
region_name='us-west-2').create_secret(
41-
Name=SECRET_NAME,
42-
SecretBinary=SECRET_BINARY)
42+
def test_get_secret_binary(binary_secret):
4343
secret = secrets.get_secret(SECRET_NAME)
4444
assert (secret == SECRET)
45-
# client.create_secret(Name=SECRET_NAME, SecretBinary=SECRET_BINARY)

0 commit comments

Comments
 (0)