feat(aws): add support for Lambda Function URLs#284
feat(aws): add support for Lambda Function URLs#284Sharayu1418 wants to merge 2 commits intospcl:masterfrom
Conversation
📝 WalkthroughWalkthroughAdds support for AWS Lambda Function URLs: new configuration keys, config-side management of Function URLs, a FunctionURLTrigger implementation, integration into trigger deserialization, and AWS operations to create/delete Function URLs with conditional trigger creation. Changes
Sequence DiagramssequenceDiagram
participant Client
participant AWS
participant AWSResources
participant Lambda
participant FunctionURL as "Function URL (HTTP)"
Client->>AWS: create_trigger(function, http_config)
AWS->>AWSResources: check use_function_url
alt use_function_url = true
AWSResources->>Lambda: ensure_function_url(function)
Lambda-->>AWSResources: function_url (url, name, auth)
AWS->>AWS: instantiate FunctionURLTrigger
AWS-->>Client: return FunctionURLTrigger
Client->>FunctionURL: HTTP invoke (sync/async)
FunctionURL-->>Client: ExecutionResult
else use_function_url = false
AWS->>Lambda: create_api_gateway_trigger(function)
Lambda-->>AWS: api_endpoint
AWS-->>Client: return HTTPTrigger (API Gateway)
Client->>API Gateway: HTTP invoke
API Gateway->>Lambda: proxy invoke
Lambda-->>Client: ExecutionResult
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
sebs/aws/config.py (1)
388-393:return Trueinsidetryblock; considerelse.Per the TRY300 hint, moving
return True(and the preceding cache-cleanup) to theelseclause of thetry/exceptimproves clarity and makes it unambiguous that the positive path only runs when no exception was raised.♻️ Proposed refactor
try: lambda_client.delete_function_url_config(FunctionName=function_name) self.logging.info(f"Deleted Function URL for {function_name}") try: lambda_client.remove_permission( FunctionName=function_name, StatementId="FunctionURLAllowPublicAccess", ) except lambda_client.exceptions.ResourceNotFoundException: pass if function_name in self._function_urls: del self._function_urls[function_name] - - return True except lambda_client.exceptions.ResourceNotFoundException: self.logging.info(f"No Function URL found for {function_name}") return False + else: + return True🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/config.py` around lines 388 - 393, The positive return and cache cleanup currently occur inside the try block and should be moved into an else clause so they only run when no exception is raised: after calling the AWS operation that may raise lambda_client.exceptions.ResourceNotFoundException, put the cache removal (del self._function_urls[function_name] if present) and the return True inside an else: block tied to that try/except, leaving the except to only log via self.logging.info(f"No Function URL found for {function_name}").sebs/aws/triggers.py (1)
152-155:ThreadPoolExecutorcreated perasync_invokecall is never shut down.A new pool is allocated on each call with no
pool.shutdown(), leaking threads until GC. This mirrors the pre-existing pattern inHTTPTrigger.async_invoke(lines 114–118). Consider sharing a single class-level executor or callingpool.shutdown(wait=False)after submitting:♻️ Proposed fix
def async_invoke(self, payload: dict) -> concurrent.futures.Future: pool = concurrent.futures.ThreadPoolExecutor() fut = pool.submit(self.sync_invoke, payload) + pool.shutdown(wait=False) return fut🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/triggers.py` around lines 152 - 155, The async_invoke implementation creates a new ThreadPoolExecutor on every call (in async_invoke) and never shuts it down, leaking threads; change this to reuse a single executor (e.g., add a class-level or instance attribute like _executor and initialize a ThreadPoolExecutor once) and have async_invoke submit to that shared executor (calling self.sync_invoke), or if you prefer per-call pools call pool.shutdown(wait=False) after submitting; reference async_invoke, sync_invoke and ThreadPoolExecutor (and mirror the fix used in HTTPTrigger.async_invoke) to locate and update the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@sebs/aws/aws.py`:
- Around line 439-444: The delete_function_url method is never invoked, leaving
entries in the in-memory/on-disk _function_urls cache orphaned when
delete_function removes the Lambda on AWS; update delete_function to call
delete_function_url(func_name) after a successful AWS delete (or in its
cleanup/finally block) so the cache and on-disk state are removed (handle and
log any exceptions from delete_function_url and preserve the original
delete_function return semantics), referencing the existing delete_function and
delete_function_url methods and the _function_urls cache for where to apply the
change.
In `@sebs/aws/config.py`:
- Around line 464-469: The code directly sets ret._function_url_auth_type which
bypasses the property setter validation; change that line to use the property
setter (ret.function_url_auth_type = dct.get("function-url-auth-type", "NONE"))
so the value is validated (must be "NONE" or "AWS_IAM"); ensure the setter on
the class (function_url_auth_type property) enforces/raises on invalid values so
bad config entries are rejected instead of silently stored.
- Around line 349-357: Change the broad except Exception block around the
Function URL creation to catch specific, retryable errors (e.g.,
botocore.exceptions.ClientError or the specific boto3 error types) instead of
all exceptions, use self.logging.exception(...) to log the full traceback
(replace self.logging.error(e)), and when giving up raise a RuntimeError(...)
from e to preserve the original exception chain; also ensure non-retryable
exceptions are re-raised immediately rather than counted toward retries
(reference the retry loop surrounding the Function URL creation and the
variables retries, self.logging, and the raised RuntimeError).
In `@sebs/aws/triggers.py`:
- Around line 148-150: sync_invoke currently calls _http_invoke which always
posts unsigned HTTP requests, so Function URLs using AWS_IAM will 403; update
sync_invoke (or _http_invoke) to check self.auth_type and, for "AWS_IAM", sign
the HTTP request with AWS SigV4 (use botocore.auth.SigV4Auth via the existing
boto3/session credentials to add the required headers) before performing the
pycurl POST, or alternatively raise a clear exception for unsupported auth
types; reference sync_invoke, _http_invoke and the self.auth_type attribute when
making the change.
---
Nitpick comments:
In `@sebs/aws/config.py`:
- Around line 388-393: The positive return and cache cleanup currently occur
inside the try block and should be moved into an else clause so they only run
when no exception is raised: after calling the AWS operation that may raise
lambda_client.exceptions.ResourceNotFoundException, put the cache removal (del
self._function_urls[function_name] if present) and the return True inside an
else: block tied to that try/except, leaving the except to only log via
self.logging.info(f"No Function URL found for {function_name}").
In `@sebs/aws/triggers.py`:
- Around line 152-155: The async_invoke implementation creates a new
ThreadPoolExecutor on every call (in async_invoke) and never shuts it down,
leaking threads; change this to reuse a single executor (e.g., add a class-level
or instance attribute like _executor and initialize a ThreadPoolExecutor once)
and have async_invoke submit to that shared executor (calling self.sync_invoke),
or if you prefer per-call pools call pool.shutdown(wait=False) after submitting;
reference async_invoke, sync_invoke and ThreadPoolExecutor (and mirror the fix
used in HTTPTrigger.async_invoke) to locate and update the code.
| def delete_function_url(self, func_name: str) -> bool: | ||
| """ | ||
| Delete the Function URL associated with a Lambda function. | ||
| Returns True if deleted successfully, False if it didn't exist. | ||
| """ | ||
| return self.config.resources.delete_function_url(func_name, self.session) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n --type=py "delete_function_url" -C3Repository: spcl/serverless-benchmarks
Length of output: 1532
🏁 Script executed:
rg -n --type=py "\.delete_function_url\("Repository: spcl/serverless-benchmarks
Length of output: 171
🏁 Script executed:
rg -n --type=py "def delete_function" -A15 sebs/aws/aws.pyRepository: spcl/serverless-benchmarks
Length of output: 1035
🏁 Script executed:
sed -n '365,390p' sebs/aws/config.pyRepository: spcl/serverless-benchmarks
Length of output: 1037
Integrate delete_function_url into the delete_function cleanup flow.
The method is well-implemented with proper cache cleanup (del self._function_urls[function_name]), but it has no callers in the codebase. When delete_function is called at line 432, it only invokes the AWS API but never calls delete_function_url, leaving the in-memory and on-disk _function_urls cache entry orphaned. This causes stale cache lookups in subsequent runs. Either invoke delete_function_url from delete_function, or document that cache cleanup must be called separately.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@sebs/aws/aws.py` around lines 439 - 444, The delete_function_url method is
never invoked, leaving entries in the in-memory/on-disk _function_urls cache
orphaned when delete_function removes the Lambda on AWS; update delete_function
to call delete_function_url(func_name) after a successful AWS delete (or in its
cleanup/finally block) so the cache and on-disk state are removed (handle and
log any exceptions from delete_function_url and preserve the original
delete_function return semantics), referencing the existing delete_function and
delete_function_url methods and the _function_urls cache for where to apply the
change.
sebs/aws/config.py
Outdated
| return self._function_name | ||
|
|
||
| @property | ||
| def auth_type(self) -> str: |
There was a problem hiding this comment.
What are the available authentication types? "None" and "IAM"? Is there any other alternative?
I think it would be cleaner with a string-based enum instead of a plain string :) This will also make it easier to create asserts (see other comments) and implement if conditions.
sebs/aws/config.py
Outdated
|
|
||
| @function_url_auth_type.setter | ||
| def function_url_auth_type(self, value: str): | ||
| if value not in ("NONE", "AWS_IAM"): |
There was a problem hiding this comment.
Makes sense, but please use Enums here.
| return cached_url | ||
|
|
||
| lambda_client = boto3_session.client( | ||
| service_name="lambda", region_name=cast(str, self._region) |
There was a problem hiding this comment.
Why is the cast necessary here? Shouldn't _region be a string?
|
|
||
| if auth_type == "NONE": | ||
| try: | ||
| lambda_client.add_permission( |
There was a problem hiding this comment.
Please use logging to print a warning here - function now has unrestricted public access. Users should be aware of that.
| FunctionUrlAuthType="NONE", | ||
| ) | ||
| except lambda_client.exceptions.ResourceConflictException: | ||
| pass |
There was a problem hiding this comment.
When does this exception happen - if we try to add a permissions to the function that already exists? That should be safe but can we add a comment explaining this to make it clear it is safe to ignore this exception
| retries = 0 | ||
| while retries < 5: | ||
| try: | ||
| response = lambda_client.create_function_url_config( |
There was a problem hiding this comment.
I don't get the logic of this code and there's no comment explaining it:
(1) When can the ResourceConflictException happen if we previously had ResourceNotFoundException?
(2) What other exceptions can happen that force us to repeat? IIRC, boto3 typically creates specific exception types that describe situations, e.g., another resource update is pending or too many requests. We should capture them explicitly to not blindly repeat loop iterations, when the error type indicates that all future requests to create function URLs will also fail.
sebs/aws/config.py
Outdated
| self.logging.error(e) | ||
| raise RuntimeError("Failed to create Function URL!") | ||
| else: | ||
| self.logging.info("Function URL creation failed, retrying...") |
There was a problem hiding this comment.
Please inform user of the increasing sleep time (linear back-off).
sebs/aws/config.py
Outdated
| self.logging.info(f"Deleted Function URL for {function_name}") | ||
|
|
||
| try: | ||
| lambda_client.remove_permission( |
There was a problem hiding this comment.
Shouldn't this be conditional on the authentication type?
|
@Sharayu1418 Thank you for the contribution, it is much appreciated! I left few comments :) |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
sebs/aws/config.py (3)
31-37:raiseinexceptblock should chain the original error.The
raise ValueError(...)inside theexcept ValueErrordiscards the original traceback. Useraise ... fromto preserve it.Proposed fix
try: return FunctionURLAuthType(value) except ValueError: - raise ValueError( + raise ValueError( f"Invalid auth type '{value}'. Must be one of: " f"{[e.value for e in FunctionURLAuthType]}" - ) + ) from None🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/config.py` around lines 31 - 37, The except block in the FunctionURLAuthType conversion swallows the original ValueError traceback; modify the except in the function that calls FunctionURLAuthType (the try/except handling the variable `value`) to re-raise the new ValueError using exception chaining (i.e., `raise ValueError(...) from e`) so the original exception is preserved; locate the try/except that returns FunctionURLAuthType(value) and change the `raise ValueError(...)` to chain from the caught exception.
403-416:logging.exception(e)— the exception argument is redundant.
logging.exceptionalready captures the active exception context automatically. Passingecauses the exception'sstr()to appear twice in the log (once as the message, once as the traceback). Pass a descriptive string instead.Proposed fix
if retries == 5: - self.logging.error("Failed to create Function URL after 5 retries!") - self.logging.exception(e) + self.logging.exception( + "Failed to create Function URL after 5 retries!" + ) raise RuntimeError("Failed to create Function URL!") from e🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/config.py` around lines 403 - 416, The self.logging.exception call is passing the exception object e redundantly; replace self.logging.exception(e) with a descriptive message (e.g. self.logging.exception("Failed to create Function URL after 5 retries")) or use self.logging.error("Failed to create Function URL after 5 retries", exc_info=True) so the stacktrace is still logged; update the block handling lambda_client.exceptions.TooManyRequestsException where retries == 5 to log a clear message without passing the exception object.
356-360: Existing Function URL's auth type may silently differ from the configured auth type.When a pre-existing Function URL is found via
get_function_url_config, itsAuthTypeis used as-is (line 360), even ifself._function_url_auth_typeis set differently. This is probably the correct behavior (respect what's deployed), but a warning when they diverge would help operators notice the mismatch.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/config.py` around lines 356 - 360, When an existing Function URL is discovered via lambda_client.get_function_url_config and you parse its auth type with FunctionURLAuthType.from_string into auth_type, compare that auth_type to the configured self._function_url_auth_type and emit a warning if they differ (include func.name and both values in the message) so operators are notified of the mismatch; update the block where response = lambda_client.get_function_url_config(...) and auth_type is set to perform this comparison and call self.logging.warning with clear context when they diverge.sebs/aws/triggers.py (2)
164-168: NewThreadPoolExecutorcreated per invocation — pre-existing pattern, butshutdownis inconsistent withHTTPTrigger.
FunctionURLTrigger.async_invokecallspool.shutdown(wait=False)on line 167, which is good practice to allow cleanup. However,HTTPTrigger.async_invoke(line 117) does not. Consider aligning both for consistency — either addshutdowntoHTTPTriggeras well, or remove it here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/triggers.py` around lines 164 - 168, The async_invoke implementation in FunctionURLTrigger calls pool.shutdown(wait=False) but HTTPTrigger.async_invoke does not, causing inconsistency; update HTTPTrigger.async_invoke to mirror FunctionURLTrigger by creating the ThreadPoolExecutor, submitting self.sync_invoke (or the equivalent HTTP sync method) and calling pool.shutdown(wait=False) after submit, or alternatively remove the shutdown call from FunctionURLTrigger.async_invoke so both implementations match — modify the HTTPTrigger.async_invoke or FunctionURLTrigger.async_invoke (referencing the async_invoke methods and the use of ThreadPoolExecutor and pool.shutdown) to make the behavior consistent across triggers.
170-185: Align serialization key naming convention betweenHTTPTriggerandFunctionURLTrigger.
HTTPTriggeruses hyphens (api-id) whileFunctionURLTriggerandAWSResources.FunctionURLboth use underscores (function_name,auth_type). Additionally,AWSResources.serialize()uses hyphens for function URL keys (function-urls,use-function-url,function-url-auth-type), creating inconsistency between the parent class and nestedFunctionURLserialization.Change both
FunctionURLTrigger.serialize()/deserialize()andAWSResources.FunctionURL.serialize()/deserialize()to use hyphens for consistency with the rest of the codebase.Proposed changes
In
sebs/aws/triggers.py:def serialize(self) -> dict: return { "type": "FunctionURL", "url": self.url, - "function_name": self.function_name, - "auth_type": self.auth_type.value, + "function-name": self.function_name, + "auth-type": self.auth_type.value, } `@staticmethod` def deserialize(obj: dict) -> Trigger: - auth_type_str = obj.get("auth_type", "NONE") + auth_type_str = obj.get("auth-type", "NONE") return FunctionURLTrigger( obj["url"], - obj["function_name"], + obj["function-name"], FunctionURLAuthType.from_string(auth_type_str), )In
sebs/aws/config.py, updateAWSResources.FunctionURL.serialize()/deserialize()similarly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sebs/aws/triggers.py` around lines 170 - 185, The FunctionURLTrigger and AWSResources.FunctionURL serializers/deserializers must use hyphenated keys to match HTTPTrigger and AWSResources conventions: update FunctionURLTrigger.serialize() to output keys "function-name" and "auth-type" (instead of "function_name"/"auth_type") and change its deserialize() to read those hyphenated keys and pass the value through FunctionURLAuthType.from_string; likewise update AWSResources.FunctionURL.serialize()/deserialize() to produce/consume "function-name", "auth-type" and any function-URL list keys to use hyphens (matching AWSResources.serialize() use of "function-urls", "use-function-url", "function-url-auth-type"); ensure all renamed keys are consistently used across both serialize and deserialize methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@sebs/aws/config.py`:
- Around line 344-349: The current NotImplementedError check for
FunctionURLAuthType.AWS_IAM runs only when creating a new URL and can be
bypassed for cached or externally-created URLs; move the AWS_IAM guard so it
executes after resolving the URL object (i.e. after function_url() returns its
value or after get_function_url_config() path) to ensure any returned URL with
auth_type == FunctionURLAuthType.AWS_IAM raises immediately; alternatively
ensure FunctionURLTrigger.sync_invoke contains the definitive guard, but if
keeping it here, inspect the resolved object’s auth_type and raise
NotImplementedError for AWS_IAM before returning the URL.
---
Nitpick comments:
In `@sebs/aws/config.py`:
- Around line 31-37: The except block in the FunctionURLAuthType conversion
swallows the original ValueError traceback; modify the except in the function
that calls FunctionURLAuthType (the try/except handling the variable `value`) to
re-raise the new ValueError using exception chaining (i.e., `raise
ValueError(...) from e`) so the original exception is preserved; locate the
try/except that returns FunctionURLAuthType(value) and change the `raise
ValueError(...)` to chain from the caught exception.
- Around line 403-416: The self.logging.exception call is passing the exception
object e redundantly; replace self.logging.exception(e) with a descriptive
message (e.g. self.logging.exception("Failed to create Function URL after 5
retries")) or use self.logging.error("Failed to create Function URL after 5
retries", exc_info=True) so the stacktrace is still logged; update the block
handling lambda_client.exceptions.TooManyRequestsException where retries == 5 to
log a clear message without passing the exception object.
- Around line 356-360: When an existing Function URL is discovered via
lambda_client.get_function_url_config and you parse its auth type with
FunctionURLAuthType.from_string into auth_type, compare that auth_type to the
configured self._function_url_auth_type and emit a warning if they differ
(include func.name and both values in the message) so operators are notified of
the mismatch; update the block where response =
lambda_client.get_function_url_config(...) and auth_type is set to perform this
comparison and call self.logging.warning with clear context when they diverge.
In `@sebs/aws/triggers.py`:
- Around line 164-168: The async_invoke implementation in FunctionURLTrigger
calls pool.shutdown(wait=False) but HTTPTrigger.async_invoke does not, causing
inconsistency; update HTTPTrigger.async_invoke to mirror FunctionURLTrigger by
creating the ThreadPoolExecutor, submitting self.sync_invoke (or the equivalent
HTTP sync method) and calling pool.shutdown(wait=False) after submit, or
alternatively remove the shutdown call from FunctionURLTrigger.async_invoke so
both implementations match — modify the HTTPTrigger.async_invoke or
FunctionURLTrigger.async_invoke (referencing the async_invoke methods and the
use of ThreadPoolExecutor and pool.shutdown) to make the behavior consistent
across triggers.
- Around line 170-185: The FunctionURLTrigger and AWSResources.FunctionURL
serializers/deserializers must use hyphenated keys to match HTTPTrigger and
AWSResources conventions: update FunctionURLTrigger.serialize() to output keys
"function-name" and "auth-type" (instead of "function_name"/"auth_type") and
change its deserialize() to read those hyphenated keys and pass the value
through FunctionURLAuthType.from_string; likewise update
AWSResources.FunctionURL.serialize()/deserialize() to produce/consume
"function-name", "auth-type" and any function-URL list keys to use hyphens
(matching AWSResources.serialize() use of "function-urls", "use-function-url",
"function-url-auth-type"); ensure all renamed keys are consistently used across
both serialize and deserialize methods.
| # Check for unsupported auth type before attempting to create | ||
| if self._function_url_auth_type == FunctionURLAuthType.AWS_IAM: | ||
| raise NotImplementedError( | ||
| "AWS_IAM authentication for Function URLs is not yet supported. " | ||
| "SigV4 request signing is required for AWS_IAM auth type. " | ||
| "Please use auth_type='NONE' or implement SigV4 signing." |
There was a problem hiding this comment.
AWS_IAM guard is only at creation time — a cached AWS_IAM URL will bypass this check.
If a Function URL with AWS_IAM auth was created outside this tool (or in a future version), function_url() returns it from the cache at line 340-342 or from the get_function_url_config path at lines 356-360 without ever hitting the NotImplementedError guard. The caller (and ultimately FunctionURLTrigger.sync_invoke) will fail later with an unsigned request producing a 403.
Consider moving the guard to after the URL object is resolved (or rely solely on the trigger-side guard in FunctionURLTrigger.sync_invoke, which already exists), rather than only blocking the creation path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@sebs/aws/config.py` around lines 344 - 349, The current NotImplementedError
check for FunctionURLAuthType.AWS_IAM runs only when creating a new URL and can
be bypassed for cached or externally-created URLs; move the AWS_IAM guard so it
executes after resolving the URL object (i.e. after function_url() returns its
value or after get_function_url_config() path) to ensure any returned URL with
auth_type == FunctionURLAuthType.AWS_IAM raises immediately; alternatively
ensure FunctionURLTrigger.sync_invoke contains the definitive guard, but if
keeping it here, inspect the resolved object’s auth_type and raise
NotImplementedError for AWS_IAM before returning the URL.
Summary
Changes
FunctionURLTriggerclass insebs/aws/triggers.py- HTTP trigger implementation using Lambda Function URLssebs/aws/config.py:use-function-url(bool, default:false) - Enable Function URLs instead of API Gatewayfunction-url-auth-type("NONE"|"AWS_IAM", default:"NONE") - Authentication typedelete_function_url()method for resource cleanupsebs/aws/aws.pyto check config flag and create appropriate trigger typeSummary by CodeRabbit
New Features
Configuration