Skip to content

Commit 8b38f5b

Browse files
Merge pull request #614 from amcmahon-rh/exclusion_paths
Support "exclude_paths" in exodus-cdn [RHELDST-26014]
2 parents 5fe0690 + d2fe3a9 commit 8b38f5b

File tree

4 files changed

+317
-46
lines changed

4 files changed

+317
-46
lines changed

exodus_lambda/functions/origin_request.py

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import gzip
44
import json
55
import os
6+
import re
67
import time
78
from base64 import b64decode
89
from datetime import datetime, timedelta, timezone
@@ -87,7 +88,7 @@ def definitions(self):
8788

8889
return out
8990

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

104105
for alias in remaining:
105-
if uri.startswith(alias["src"] + "/") or uri == alias["src"]:
106+
exclusion_match = not ignore_exclusions and any(
107+
[
108+
re.search(exclusion, uri)
109+
for exclusion in alias.get("exclude_paths", [])
110+
]
111+
)
112+
if (
113+
uri.startswith(alias["src"] + "/") or uri == alias["src"]
114+
) and not exclusion_match:
106115
uri = uri.replace(alias["src"], alias["dest"], 1)
107116
processed.append(alias)
108117

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

118127
return uri
119128

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

132+
uri = self.uri_alias(
133+
uri, self.definitions.get("origin_alias"), ignore_exclusions
134+
)
124135
# aliases relating to rhui; listing files are a special exemption
125136
# because they must be allowed to differ for rhui vs non-rhui.
126137
if not uri.endswith("/listing"):
127-
uri = self.uri_alias(uri, self.definitions.get("rhui_alias"))
138+
uri = self.uri_alias(
139+
uri, self.definitions.get("rhui_alias"), ignore_exclusions
140+
)
128141

129142
# aliases relating to releasever; e.g. /content/dist/rhel8/8 <=> /content/dist/rhel8/8.5
130-
uri = self.uri_alias(uri, self.definitions.get("releasever_alias"))
143+
uri = self.uri_alias(
144+
uri, self.definitions.get("releasever_alias"), ignore_exclusions
145+
)
131146

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

@@ -304,33 +319,8 @@ def validate_request(self, request):
304319
valid = False
305320
return valid
306321

307-
def handler(self, event, context):
308-
# pylint: disable=unused-argument
309-
310-
request = event["Records"][0]["cf"]["request"]
311-
312-
if not self.validate_request(request):
313-
return {"status": "400", "statusDescription": "Bad Request"}
314-
315-
self.logger.debug(
316-
"Incoming request value for origin_request",
317-
extra={"request": request},
318-
)
319-
320-
request["uri"] = unquote(request["uri"])
321-
original_uri = request["uri"]
322-
323-
if request["uri"].startswith("/_/cookie/"):
324-
return self.handle_cookie_request(event)
325-
326-
uri = self.resolve_aliases(request["uri"])
327-
328-
listing_response = self.handle_listing_request(uri)
329-
if listing_response:
330-
self.set_cache_control(uri, listing_response)
331-
return listing_response
332-
333-
table = self.conf["table"]["name"]
322+
def handle_file_request(self, request, table, original_uri, uri):
323+
# Try find the db entry corresponding to the uri.
334324

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

376366
return out
377-
self.logger.info("No item found for URI: %s", uri)
367+
368+
def handler(self, event, context):
369+
# pylint: disable=unused-argument
370+
request = event["Records"][0]["cf"]["request"]
371+
372+
if not self.validate_request(request):
373+
return {"status": "400", "statusDescription": "Bad Request"}
374+
375+
self.logger.debug(
376+
"Incoming request value for origin_request",
377+
extra={"request": request},
378+
)
379+
380+
request["uri"] = unquote(request["uri"])
381+
original_uri = request["uri"]
382+
383+
if request["uri"].startswith("/_/cookie/"):
384+
return self.handle_cookie_request(event)
385+
386+
preferred_uri = self.resolve_aliases(request["uri"])
387+
fallback_uri = self.resolve_aliases(
388+
request["uri"], ignore_exclusions=True
389+
)
390+
uris = [preferred_uri]
391+
# Some file keys might take a while to update to reflect URI alias exclusions.
392+
# Allowing the original behaviour as a fallback will avoid a flood of 404 errors
393+
if preferred_uri != fallback_uri:
394+
uris.append(fallback_uri)
395+
396+
for uri in uris:
397+
if listing_response := self.handle_listing_request(uri):
398+
self.set_cache_control(uri, listing_response)
399+
return listing_response
400+
401+
table = self.conf["table"]["name"]
402+
if out := self.handle_file_request(
403+
request, table, original_uri, uri
404+
):
405+
return out
406+
407+
self.logger.info("No item found for URI: %s", uri)
378408
return {"status": "404", "statusDescription": "Not Found"}
379409

380410

tests/functions/test_alias.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# More in depth tests for alias resolution.
22
from collections import namedtuple
33

4+
import pytest
5+
46
from exodus_lambda.functions.origin_request import OriginRequest
57

68
from ..test_utils.utils import generate_test_config
79

810
TEST_CONF = generate_test_config()
911

10-
Alias = namedtuple("Alias", ["src", "dest"])
12+
Alias = namedtuple("Alias", ["src", "dest", "exclude_paths"])
1113

1214

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

4850
assert req.uri_alias("/foo/bar", aliases) == "/quux"
51+
52+
53+
@pytest.mark.parametrize(
54+
"uri, expected_uri, ignore_exclusions, aliases",
55+
[
56+
(
57+
"/origin/path/dir/filename.ext",
58+
"/origin/path/dir/filename.ext",
59+
False,
60+
[
61+
{
62+
"src": "/origin/path",
63+
"dest": "/alias",
64+
"exclude_paths": ["/dir/"],
65+
}
66+
],
67+
),
68+
(
69+
"/origin/path/dir/filename.ext",
70+
"/alias/dir/filename.ext",
71+
False,
72+
[
73+
{
74+
"src": "/origin/path",
75+
"dest": "/alias",
76+
"exclude_paths": ["/banana/"],
77+
}
78+
],
79+
),
80+
(
81+
"/origin/path/c/dir/filename.ext",
82+
"/second/step/c/dir/filename.ext",
83+
False,
84+
[
85+
{
86+
"src": "/origin/path",
87+
"dest": "/first/step",
88+
"exclude_paths": ["/a/"],
89+
},
90+
{"src": "/first", "dest": "/second", "exclude_paths": ["/b/"]},
91+
{"src": "/second", "dest": "/third", "exclude_paths": ["/c/"]},
92+
],
93+
),
94+
(
95+
"/origin/path/rhel7/dir/filename.ext",
96+
"/aliased/path/rhel7/dir/filename.ext",
97+
False,
98+
[
99+
{
100+
"src": "/origin",
101+
"dest": "/aliased",
102+
"exclude_paths": ["/rhel[89]/"],
103+
},
104+
],
105+
),
106+
(
107+
"/origin/path/rhel9/dir/filename.ext",
108+
"/origin/path/rhel9/dir/filename.ext",
109+
False,
110+
[
111+
{
112+
"src": "/origin",
113+
"dest": "/aliased",
114+
"exclude_paths": ["/rhel[89]/"],
115+
},
116+
],
117+
),
118+
(
119+
"/origin/path/rhel9/dir/filename.ext",
120+
"/aliased/path/rhel9/dir/filename.ext",
121+
True,
122+
[
123+
{
124+
"src": "/origin",
125+
"dest": "/aliased",
126+
"exclude_paths": ["/rhel9/"],
127+
},
128+
],
129+
),
130+
(
131+
"/origin/path/rhel9/dir/filename.ext",
132+
"/aliased/path/rhel9/dir/filename.ext",
133+
True,
134+
[
135+
{
136+
"src": "/origin",
137+
"dest": "/aliased",
138+
"exclude_paths": ["/rhel7/"],
139+
},
140+
],
141+
),
142+
],
143+
ids=[
144+
"excluded",
145+
"not excluded",
146+
"multilevel",
147+
"regex not excluded",
148+
"regex excluded",
149+
"exclusion ignored matching",
150+
"exclusion ignored no match",
151+
],
152+
)
153+
def test_alias_exclusions(uri, expected_uri, ignore_exclusions, aliases):
154+
"""Paths exactly matching an alias can be resolved."""
155+
156+
req = OriginRequest(conf_file=TEST_CONF)
157+
158+
assert req.uri_alias(uri, aliases, ignore_exclusions) == expected_uri

0 commit comments

Comments
 (0)