Skip to content

Commit

Permalink
Merge pull request #614 from amcmahon-rh/exclusion_paths
Browse files Browse the repository at this point in the history
Support "exclude_paths" in exodus-cdn [RHELDST-26014]
  • Loading branch information
crungehottman authored Jan 7, 2025
2 parents 5fe0690 + d2fe3a9 commit 8b38f5b
Show file tree
Hide file tree
Showing 4 changed files with 317 additions and 46 deletions.
98 changes: 64 additions & 34 deletions exodus_lambda/functions/origin_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import gzip
import json
import os
import re
import time
from base64 import b64decode
from datetime import datetime, timedelta, timezone
Expand Down Expand Up @@ -87,7 +88,7 @@ def definitions(self):

return out

def uri_alias(self, uri, aliases):
def uri_alias(self, uri, aliases, ignore_exclusions=False):
# Resolve every alias between paths within the uri (e.g.
# allow RHUI paths to be aliased to non-RHUI).
#
Expand All @@ -102,7 +103,15 @@ def uri_alias(self, uri, aliases):
processed = []

for alias in remaining:
if uri.startswith(alias["src"] + "/") or uri == alias["src"]:
exclusion_match = not ignore_exclusions and any(
[
re.search(exclusion, uri)
for exclusion in alias.get("exclude_paths", [])
]
)
if (
uri.startswith(alias["src"] + "/") or uri == alias["src"]
) and not exclusion_match:
uri = uri.replace(alias["src"], alias["dest"], 1)
processed.append(alias)

Expand All @@ -117,17 +126,23 @@ def uri_alias(self, uri, aliases):

return uri

def resolve_aliases(self, uri):
def resolve_aliases(self, uri, ignore_exclusions=False):
# aliases relating to origin, e.g. content/origin <=> origin
uri = self.uri_alias(uri, self.definitions.get("origin_alias"))

uri = self.uri_alias(
uri, self.definitions.get("origin_alias"), ignore_exclusions
)
# aliases relating to rhui; listing files are a special exemption
# because they must be allowed to differ for rhui vs non-rhui.
if not uri.endswith("/listing"):
uri = self.uri_alias(uri, self.definitions.get("rhui_alias"))
uri = self.uri_alias(
uri, self.definitions.get("rhui_alias"), ignore_exclusions
)

# aliases relating to releasever; e.g. /content/dist/rhel8/8 <=> /content/dist/rhel8/8.5
uri = self.uri_alias(uri, self.definitions.get("releasever_alias"))
uri = self.uri_alias(
uri, self.definitions.get("releasever_alias"), ignore_exclusions
)

self.logger.debug("Resolved request URI: %s", uri)

Expand Down Expand Up @@ -304,33 +319,8 @@ def validate_request(self, request):
valid = False
return valid

def handler(self, event, context):
# pylint: disable=unused-argument

request = event["Records"][0]["cf"]["request"]

if not self.validate_request(request):
return {"status": "400", "statusDescription": "Bad Request"}

self.logger.debug(
"Incoming request value for origin_request",
extra={"request": request},
)

request["uri"] = unquote(request["uri"])
original_uri = request["uri"]

if request["uri"].startswith("/_/cookie/"):
return self.handle_cookie_request(event)

uri = self.resolve_aliases(request["uri"])

listing_response = self.handle_listing_request(uri)
if listing_response:
self.set_cache_control(uri, listing_response)
return listing_response

table = self.conf["table"]["name"]
def handle_file_request(self, request, table, original_uri, uri):
# Try find the db entry corresponding to the uri.

# Do not permit clients to explicitly request an index file
if not uri.endswith("/" + self.index):
Expand Down Expand Up @@ -374,7 +364,47 @@ def handler(self, event, context):
return response

return out
self.logger.info("No item found for URI: %s", uri)

def handler(self, event, context):
# pylint: disable=unused-argument
request = event["Records"][0]["cf"]["request"]

if not self.validate_request(request):
return {"status": "400", "statusDescription": "Bad Request"}

self.logger.debug(
"Incoming request value for origin_request",
extra={"request": request},
)

request["uri"] = unquote(request["uri"])
original_uri = request["uri"]

if request["uri"].startswith("/_/cookie/"):
return self.handle_cookie_request(event)

preferred_uri = self.resolve_aliases(request["uri"])
fallback_uri = self.resolve_aliases(
request["uri"], ignore_exclusions=True
)
uris = [preferred_uri]
# Some file keys might take a while to update to reflect URI alias exclusions.
# Allowing the original behaviour as a fallback will avoid a flood of 404 errors
if preferred_uri != fallback_uri:
uris.append(fallback_uri)

for uri in uris:
if listing_response := self.handle_listing_request(uri):
self.set_cache_control(uri, listing_response)
return listing_response

table = self.conf["table"]["name"]
if out := self.handle_file_request(
request, table, original_uri, uri
):
return out

self.logger.info("No item found for URI: %s", uri)
return {"status": "404", "statusDescription": "Not Found"}


Expand Down
112 changes: 111 additions & 1 deletion tests/functions/test_alias.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# More in depth tests for alias resolution.
from collections import namedtuple

import pytest

from exodus_lambda.functions.origin_request import OriginRequest

from ..test_utils.utils import generate_test_config

TEST_CONF = generate_test_config()

Alias = namedtuple("Alias", ["src", "dest"])
Alias = namedtuple("Alias", ["src", "dest", "exclude_paths"])


def test_alias_single():
Expand Down Expand Up @@ -46,3 +48,111 @@ def test_alias_equal():
aliases = [{"src": "/foo/bar", "dest": "/quux"}]

assert req.uri_alias("/foo/bar", aliases) == "/quux"


@pytest.mark.parametrize(
"uri, expected_uri, ignore_exclusions, aliases",
[
(
"/origin/path/dir/filename.ext",
"/origin/path/dir/filename.ext",
False,
[
{
"src": "/origin/path",
"dest": "/alias",
"exclude_paths": ["/dir/"],
}
],
),
(
"/origin/path/dir/filename.ext",
"/alias/dir/filename.ext",
False,
[
{
"src": "/origin/path",
"dest": "/alias",
"exclude_paths": ["/banana/"],
}
],
),
(
"/origin/path/c/dir/filename.ext",
"/second/step/c/dir/filename.ext",
False,
[
{
"src": "/origin/path",
"dest": "/first/step",
"exclude_paths": ["/a/"],
},
{"src": "/first", "dest": "/second", "exclude_paths": ["/b/"]},
{"src": "/second", "dest": "/third", "exclude_paths": ["/c/"]},
],
),
(
"/origin/path/rhel7/dir/filename.ext",
"/aliased/path/rhel7/dir/filename.ext",
False,
[
{
"src": "/origin",
"dest": "/aliased",
"exclude_paths": ["/rhel[89]/"],
},
],
),
(
"/origin/path/rhel9/dir/filename.ext",
"/origin/path/rhel9/dir/filename.ext",
False,
[
{
"src": "/origin",
"dest": "/aliased",
"exclude_paths": ["/rhel[89]/"],
},
],
),
(
"/origin/path/rhel9/dir/filename.ext",
"/aliased/path/rhel9/dir/filename.ext",
True,
[
{
"src": "/origin",
"dest": "/aliased",
"exclude_paths": ["/rhel9/"],
},
],
),
(
"/origin/path/rhel9/dir/filename.ext",
"/aliased/path/rhel9/dir/filename.ext",
True,
[
{
"src": "/origin",
"dest": "/aliased",
"exclude_paths": ["/rhel7/"],
},
],
),
],
ids=[
"excluded",
"not excluded",
"multilevel",
"regex not excluded",
"regex excluded",
"exclusion ignored matching",
"exclusion ignored no match",
],
)
def test_alias_exclusions(uri, expected_uri, ignore_exclusions, aliases):
"""Paths exactly matching an alias can be resolved."""

req = OriginRequest(conf_file=TEST_CONF)

assert req.uri_alias(uri, aliases, ignore_exclusions) == expected_uri
Loading

0 comments on commit 8b38f5b

Please sign in to comment.