Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make S3 bucket expiry deletion handling robust. (#11156) #11161

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build-support/bin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ python_tests(
pex_binary(
name = 'bootstrap_and_deploy_ci_pants_pex',
sources = ['bootstrap_and_deploy_ci_pants_pex.py'],
)
)

pex_binary(
name = 'check_banned_imports',
Expand Down Expand Up @@ -80,3 +80,5 @@ pex_binary(
name = "packages",
sources = ["packages.py"],
)

python_tests()
60 changes: 43 additions & 17 deletions build-support/bin/bootstrap_and_deploy_ci_pants_pex.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import os
import subprocess
from collections import Counter
from pathlib import Path

from common import banner, die
Expand Down Expand Up @@ -105,6 +106,44 @@ def calculate_native_engine_so_hash() -> str:
)


class ListingError(Exception):
pass


class NonUniqueVersionError(Exception):
pass


def _s3_listing_has_unique_version(listing_prefix: str, listing_result: str) -> bool:
if not listing_result:
return False
result = json.loads(listing_result)
versions = result.get("Versions")
if not versions:
return False
tally = Counter(version["Key"] for version in versions)
if len(tally) > 1:
keys = "\n".join(f"{index}.) {key}" for index, key in enumerate(tally.keys(), start=1))
raise ListingError(
f"Multiple keys returned listing {listing_prefix} in AWS S3:\n{keys}\n"
"This is unexpected. Please raise this failure in the #infra channel in Slack so we "
"can investigate."
)
delete_markers = result.get("DeleteMarkers", [])
for delete_marker in delete_markers:
key = delete_marker["Key"]
if key in tally:
tally[key] -= 1
_, count = tally.popitem()
if count > 1:
raise NonUniqueVersionError(
f"Multiple copies found of {listing_prefix} in AWS S3. This is not allowed as a "
"security precaution. Please raise this failure in the #infra channel in Slack so that "
"we may investigate how this happened and delete the duplicate copy from S3."
)
return count == 1


def native_engine_so_in_s3_cache(*, aws_bucket: str, native_engine_so_aws_key: str) -> bool:
ls_output = subprocess.run(
[
Expand All @@ -121,23 +160,10 @@ def native_engine_so_in_s3_cache(*, aws_bucket: str, native_engine_so_aws_key: s
stdout=subprocess.PIPE,
check=True,
).stdout.decode()
if not ls_output:
return False
versions = json.loads(ls_output).get("Versions")
if not versions:
return False
if len(versions) > 1:
die(
f"Multiple copies found of {native_engine_so_aws_key} in AWS S3. This is not allowed "
"as a security precaution. Please raise this failure in the #infra channel "
"in Slack so that we may investigate how this happened and delete the duplicate "
"copy from S3."
)
if not versions[0]["IsLatest"]:
# If the single version is not the latest, it means there is a delete marker that
# supersedes it.
return False
return True
try:
return _s3_listing_has_unique_version(native_engine_so_aws_key, ls_output)
except NonUniqueVersionError as e:
die(str(e))


def bootstrap_pants_pex(python_version: float) -> None:
Expand Down
157 changes: 157 additions & 0 deletions build-support/bin/bootstrap_and_deploy_ci_pants_pex_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import pytest
from bootstrap_and_deploy_ci_pants_pex import (
ListingError,
NonUniqueVersionError,
_s3_listing_has_unique_version,
)


def test_listing_has_unique_version_no_results() -> None:
assert not _s3_listing_has_unique_version("prefix", "")
assert not _s3_listing_has_unique_version("prefix", "{}")


def test_listing_has_unique_version_nominal() -> None:
assert _s3_listing_has_unique_version(
"prefix",
"""
{
"Versions": [
{
"ETag": "\\"42e2a84a76d7377e5c5a2c5c3e2e2afe\\"",
"Size": 207011168,
"StorageClass": "STANDARD",
"Key": "prefix1",
"VersionId": "0_x4c45bLKFS_zOETFrBB7u0HtZWD_j3",
"IsLatest": false,
"LastModified": "2020-10-11T11:23:32+00:00",
"Owner": {
"ID": "65a011a29cdf8ec533ec3d1ccaae921c"
}
}
]
}
""",
)


def test_listing_has_unique_version_deleted() -> None:
assert not _s3_listing_has_unique_version(
"prefix",
"""
{
"Versions": [
{
"ETag": "\\"42e2a84a76d7377e5c5a2c5c3e2e2afe\\"",
"Size": 207011168,
"StorageClass": "STANDARD",
"Key": "prefix1",
"VersionId": "0_x4c45bLKFS_zOETFrBB7u0HtZWD_j3",
"IsLatest": false,
"LastModified": "2020-10-11T11:23:32+00:00",
"Owner": {
"ID": "65a011a29cdf8ec533ec3d1ccaae921c"
}
}
],
"DeleteMarkers": [
{
"Key": "prefix1",
"VersionId": "7h5go2iDSRrWPVX8hvDITmWNb0SrHnD_",
"IsLatest": true,
"LastModified": "2020-11-11T00:00:00+00:00"
}
]
}
""",
)

assert _s3_listing_has_unique_version(
"prefix",
"""
{
"Versions": [
{
"Key": "prefix1"
},
{
"Key": "prefix1"
}
],
"DeleteMarkers": [
{
"Key": "prefix1"
}
]
}
""",
)

assert not _s3_listing_has_unique_version(
"prefix",
"""
{
"Versions": [
{
"Key": "prefix1"
},
{
"Key": "prefix1"
}
],
"DeleteMarkers": [
{
"Key": "prefix1"
},
{
"Key": "prefix1"
}
]
}
""",
)


def test_listing_has_unique_version_multiple() -> None:
with pytest.raises(ListingError):
assert _s3_listing_has_unique_version(
"prefix",
"""
{
"Versions": [
{
"Key": "prefix1"
},
{
"Key": "prefix2"
}
]
}
""",
)


def test_listing_has_unique_version_non_unique() -> None:
with pytest.raises(NonUniqueVersionError):
assert _s3_listing_has_unique_version(
"prefix",
"""
{
"Versions": [
{
"Key": "prefix1",
"VersionId": "1_x4c45bLKFS_zOETFrBB7u0HtZWD_j3",
"LastModified": "2020-10-11T11:23:32+00:00"
},
{
"Key": "prefix1",
"VersionId": "0_x4c45bLKFS_zOETFrBB7u0HtZWD_j3",
"LastModified": "2020-9-11T11:23:32+00:00"
}
]
}
""",
)
2 changes: 1 addition & 1 deletion src/python/pants/backend/project_info/list_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def register_options(cls, register):
"Display these columns when --provides is specified. Available columns are: "
"address, artifact_id, repo_name, repo_url, push_db_basedir"
),
removal_version="2.0.1.dev0",
removal_version="2.1.0.dev0",
removal_hint=(
"The option `--provides-columns` no longer does anything. It was specific to the "
"JVM backend, so no longer makes sense with Pants 2.0 initially only supporting "
Expand Down