Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bd3370cd9 |
@@ -78,29 +78,6 @@ def _immutable_head(endpoint, bucket, key, aws_env):
|
|||||||
raise RuntimeError(f"head-object failed for s3://{bucket}/{key}: {error.strip()}")
|
raise RuntimeError(f"head-object failed for s3://{bucket}/{key}: {error.strip()}")
|
||||||
|
|
||||||
|
|
||||||
def _existing_immutable_keys(endpoint, bucket, aws_env):
|
|
||||||
"""Enumerate publisher-owned immutable keys so route moves cannot delete them."""
|
|
||||||
args = ["aws", "--endpoint-url", endpoint, "s3api", "list-objects-v2",
|
|
||||||
"--bucket", bucket, "--query", "Contents[].Key", "--output", "json"]
|
|
||||||
result = _aws_capture(args, aws_env)
|
|
||||||
if result.returncode != 0:
|
|
||||||
error = f"{result.stdout}\n{result.stderr}".strip()
|
|
||||||
raise RuntimeError(f"list-objects-v2 failed for s3://{bucket}: {error}")
|
|
||||||
keys = json.loads(result.stdout) or []
|
|
||||||
immutable = []
|
|
||||||
for key in keys:
|
|
||||||
info = _immutable_head(endpoint, bucket, key, aws_env)
|
|
||||||
if info is None:
|
|
||||||
continue
|
|
||||||
directives = {
|
|
||||||
part.strip().lower().split("=", 1)[0]
|
|
||||||
for part in (info.get("CacheControl") or "").split(",")
|
|
||||||
}
|
|
||||||
if "immutable" in directives:
|
|
||||||
immutable.append(key)
|
|
||||||
return sorted(immutable)
|
|
||||||
|
|
||||||
|
|
||||||
def _immutable_digests(source, cache_control, content_type):
|
def _immutable_digests(source, cache_control, content_type):
|
||||||
with source.open("rb") as stream:
|
with source.open("rb") as stream:
|
||||||
content_digest = hashlib.file_digest(stream, "sha256").hexdigest()
|
content_digest = hashlib.file_digest(stream, "sha256").hexdigest()
|
||||||
@@ -195,7 +172,6 @@ def s3_sync(artifact, route, site_dir, credential_env_names=None):
|
|||||||
destination = f"{bucket_destination}{object_prefix + '/' if object_prefix else ''}"
|
destination = f"{bucket_destination}{object_prefix + '/' if object_prefix else ''}"
|
||||||
default_cache = next(rule["cache_control"] for rule in artifact["cache_rules"] if not rule["path"])
|
default_cache = next(rule["cache_control"] for rule in artifact["cache_rules"] if not rule["path"])
|
||||||
immutable_paths = [rule["path"] for rule in artifact["cache_rules"] if _is_immutable(rule)]
|
immutable_paths = [rule["path"] for rule in artifact["cache_rules"] if _is_immutable(rule)]
|
||||||
existing_immutable = _existing_immutable_keys(endpoint, bucket, aws_env)
|
|
||||||
# `excludes` are patterns (site.yaml `excludes:` list) that should never
|
# `excludes` are patterns (site.yaml `excludes:` list) that should never
|
||||||
# be uploaded *and* should never be deleted from the bucket — escape hatch
|
# be uploaded *and* should never be deleted from the bucket — escape hatch
|
||||||
# for assets managed out-of-band (e.g. large PDFs uploaded via aws-cli).
|
# for assets managed out-of-band (e.g. large PDFs uploaded via aws-cli).
|
||||||
@@ -212,7 +188,6 @@ def s3_sync(artifact, route, site_dir, credential_env_names=None):
|
|||||||
stage = None
|
stage = None
|
||||||
sync_source = html_dir
|
sync_source = html_dir
|
||||||
sync_excludes = [*exclude_args,
|
sync_excludes = [*exclude_args,
|
||||||
*(arg for key in existing_immutable for arg in ("--exclude", key)),
|
|
||||||
*(arg for path in immutable_paths for arg in ("--exclude", f"{path}/*"))]
|
*(arg for path in immutable_paths for arg in ("--exclude", f"{path}/*"))]
|
||||||
if object_prefix:
|
if object_prefix:
|
||||||
stage = tempfile.TemporaryDirectory()
|
stage = tempfile.TemporaryDirectory()
|
||||||
@@ -223,7 +198,6 @@ def s3_sync(artifact, route, site_dir, credential_env_names=None):
|
|||||||
sync_excludes = [
|
sync_excludes = [
|
||||||
*(arg for pattern in artifact["excludes"]
|
*(arg for pattern in artifact["excludes"]
|
||||||
for arg in ("--exclude", f"{object_prefix}/{pattern}")),
|
for arg in ("--exclude", f"{object_prefix}/{pattern}")),
|
||||||
*(arg for key in existing_immutable for arg in ("--exclude", key)),
|
|
||||||
*(arg for path in immutable_paths
|
*(arg for path in immutable_paths
|
||||||
for arg in ("--exclude", f"{object_prefix}/{path}/*")),
|
for arg in ("--exclude", f"{object_prefix}/{path}/*")),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -389,9 +389,6 @@ class PublishingTests(unittest.TestCase):
|
|||||||
with patch.dict(os.environ, {
|
with patch.dict(os.environ, {
|
||||||
"DIST_S3_ACCESS_KEY": "dist-key", "DIST_S3_SECRET_KEY": secret
|
"DIST_S3_ACCESS_KEY": "dist-key", "DIST_S3_SECRET_KEY": secret
|
||||||
}, clear=False), patch.object(deploy, "run", side_effect=capture), \
|
}, clear=False), patch.object(deploy, "run", side_effect=capture), \
|
||||||
patch.object(deploy, "_existing_immutable_keys", return_value=[
|
|
||||||
"old-dist/releases/preserved.js"
|
|
||||||
]), \
|
|
||||||
patch.object(deploy, "publish_immutable_rule") as immutable_publish, \
|
patch.object(deploy, "publish_immutable_rule") as immutable_publish, \
|
||||||
redirect_stdout(output):
|
redirect_stdout(output):
|
||||||
deploy.s3_sync(artifact, route, root)
|
deploy.s3_sync(artifact, route, root)
|
||||||
@@ -403,7 +400,6 @@ class PublishingTests(unittest.TestCase):
|
|||||||
rendered = [" ".join(command) for command, _ in commands]
|
rendered = [" ".join(command) for command, _ in commands]
|
||||||
self.assertIn("s3://baseline-dist/", rendered[0])
|
self.assertIn("s3://baseline-dist/", rendered[0])
|
||||||
self.assertIn("dist/releases/*", rendered[0])
|
self.assertIn("dist/releases/*", rendered[0])
|
||||||
self.assertIn("old-dist/releases/preserved.js", rendered[0])
|
|
||||||
self.assertTrue(all("s3://baseline-dist/dist/" in command for command in rendered[1:]))
|
self.assertTrue(all("s3://baseline-dist/dist/" in command for command in rendered[1:]))
|
||||||
self.assertTrue(any("channels/" in command and
|
self.assertTrue(any("channels/" in command and
|
||||||
"public, max-age=0, must-revalidate" in command
|
"public, max-age=0, must-revalidate" in command
|
||||||
|
|||||||
Reference in New Issue
Block a user