Skip to content

Commit 61c50c8

Browse files
wangshamonkut
andauthored
Feature/function url (#1183)
* wip * deployment working. next: delete policy * added test cases * remove handler update * Update tests.py added setting test * fix test cases * update permission configuration * default function_url_enabled to false * Update core.py fix linting error * make cors optional * Update core.py * Update core.py * merge with upstream * Update core.py * fix test cases --------- Co-authored-by: monkut <[email protected]>
1 parent e7d5bc9 commit 61c50c8

File tree

9 files changed

+605
-27
lines changed

9 files changed

+605
-27
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,18 @@ to change Zappa's behavior. Use these at your own risk!
897897
"assume_policy": "my_assume_policy.json", // optional, IAM assume policy JSON file
898898
"attach_policy": "my_attach_policy.json", // optional, IAM attach policy JSON file
899899
"apigateway_policy": "my_apigateway_policy.json", // optional, API Gateway resource policy JSON file
900+
"function_url_enabled": false, // optional, set to true if you want to enable function URL. Default false.
901+
"function_url_config": {
902+
"authorizer": "NONE", // required if function url is enabled. default None. https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html
903+
"cors": { // set to false if disable cors.
904+
"allowedOrigins": [], // The origins that can access your function URL. default [*]
905+
"allowedHeaders": [], // The HTTP headers that origins can include in requests to your function URL.
906+
"allowedMethods": [], // The HTTP methods that are allowed when calling your function URL. For example: GET , POST , DELETE , or the wildcard character (* ). default [*]
907+
"allowCredentials": false, //required, Whether to allow cookies or other credentials in requests to your function URL. default false.
908+
"exposedResponseHeaders": [], The HTTP headers in your function response that you want to expose to origins that call your function URL.
909+
"maxAge": 0 // The maximum amount of time, in seconds, that web browsers can cache results of a preflight request. default 0.
910+
}
911+
},
900912
"architecture": "x86_64", // optional, Set Lambda Architecture, defaults to x86_64. For Graviton 2 use: arm64
901913
"async_source": "sns", // Source of async tasks. Defaults to "lambda"
902914
"async_resources": true, // Create the SNS topic and DynamoDB table to use. Defaults to true.

test_settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@
135135
"lambda_concurrency_enabled": {
136136
"extends": "ttt888",
137137
"lambda_concurrency": 6
138+
},
139+
"function_url_enabled": {
140+
"extends": "ttt888",
141+
"function_url_enabled": true
138142
},
139143
"addtextmimetypes": {
140144
"s3_bucket": "lmbda",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"status_code": 201,
3+
"data": {
4+
"ResponseMetadata": {
5+
"HTTPStatusCode": 201,
6+
"RetryAttempts": 0
7+
},
8+
"FunctionUrl": "https://xxxxx.lambda-url.ap-southeast-1.on.aws/",
9+
"FunctionArn": "arn:aws:lambda:ap-southeast-1:123456789:function:dev"
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"status_code": 200,
3+
"data": {
4+
"ResponseMetadata": {
5+
"HTTPStatusCode": 200
6+
},
7+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
8+
"FunctionArn": "1111111"
9+
}
10+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"status_code": 200,
3+
"data": {
4+
"ResponseMetadata": {
5+
"HTTPStatusCode": 200
6+
},
7+
"FunctionUrlConfigs": [
8+
{
9+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
10+
"FunctionArn": "1111111"
11+
}
12+
]
13+
}
14+
}

tests/tests.py

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,12 @@ def test_load_settings__lambda_concurrency_enabled(self):
14891489
zappa_cli.load_settings("test_settings.json")
14901490
self.assertEqual(6, zappa_cli.stage_config["lambda_concurrency"])
14911491

1492+
def test_load_settings__function_url_enabled(self):
1493+
zappa_cli = ZappaCLI()
1494+
zappa_cli.api_stage = "function_url_enabled"
1495+
zappa_cli.load_settings("test_settings.json")
1496+
self.assertEqual(True, zappa_cli.stage_config["function_url_enabled"])
1497+
14921498
def test_load_settings_yml(self):
14931499
zappa_cli = ZappaCLI()
14941500
zappa_cli.api_stage = "ttt888"
@@ -1938,6 +1944,7 @@ def test_certify_sanity_checks(self):
19381944
"""
19391945
zappa_cli = ZappaCLI()
19401946
zappa_cli.domain = "test.example.com"
1947+
zappa_cli.use_apigateway = True
19411948
try:
19421949
zappa_cli.certify()
19431950
except AttributeError:
@@ -2782,6 +2789,261 @@ def test_delete_lambda_concurrency(self, client):
27822789
FunctionName="abc",
27832790
)
27842791

2792+
@mock.patch("botocore.client")
2793+
def test_deploy_lambda_function_url(self, client):
2794+
boto_mock = mock.MagicMock()
2795+
zappa_core = Zappa(
2796+
boto_session=boto_mock,
2797+
profile_name="test",
2798+
aws_region="test",
2799+
load_credentials=True,
2800+
)
2801+
function_name = "abc"
2802+
function_url_config = {
2803+
"authorizer": "NONE",
2804+
"cors": {
2805+
"allowedOrigins": ["*"],
2806+
"allowedHeaders": ["*"],
2807+
"allowedMethods": ["*"],
2808+
"allowCredentials": False,
2809+
"exposedResponseHeaders": ["*"],
2810+
"maxAge": 0,
2811+
},
2812+
}
2813+
zappa_core.lambda_client.create_function_url_config.return_value = {
2814+
"ResponseMetadata": {
2815+
"HTTPStatusCode": 201,
2816+
"RetryAttempts": 0,
2817+
},
2818+
"FunctionUrl": "https://xxxxx.lambda-url.ap-southeast-1.on.aws/",
2819+
"FunctionArn": "arn:aws:lambda:ap-southeast-1:123456789:function:{}".format(function_name),
2820+
}
2821+
zappa_core.lambda_client.add_permission.return_value = {
2822+
"ResponseMetadata": {
2823+
"RequestId": "cbe73d4e-007e-4476-a4a0-fbd07599570a",
2824+
"HTTPStatusCode": 201,
2825+
"RetryAttempts": 0,
2826+
},
2827+
"Statement": '{"Sid":"FunctionURLAllowPublicAccess","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunctionUrl","Resource":"arn:aws:lambda:ap-southeast-1:123456789:function:abc"}, "Condition":{"StringEquals":{"lambda: FunctionUrlAuthType":"NONE"}}',
2828+
}
2829+
2830+
zappa_core.deploy_lambda_function_url(function_name="abc", function_url_config=function_url_config)
2831+
boto_mock.client().create_function_url_config.assert_called_with(
2832+
FunctionName=function_name,
2833+
AuthType=function_url_config["authorizer"],
2834+
Cors={
2835+
"AllowCredentials": function_url_config["cors"]["allowCredentials"],
2836+
"AllowHeaders": function_url_config["cors"]["allowedHeaders"],
2837+
"AllowMethods": function_url_config["cors"]["allowedMethods"],
2838+
"AllowOrigins": function_url_config["cors"]["allowedOrigins"],
2839+
"ExposeHeaders": function_url_config["cors"]["exposedResponseHeaders"],
2840+
"MaxAge": function_url_config["cors"]["maxAge"],
2841+
},
2842+
)
2843+
2844+
boto_mock.client().add_permission.assert_called_with(
2845+
FunctionName=function_name,
2846+
StatementId="FunctionURLAllowPublicAccess",
2847+
Action="lambda:InvokeFunctionUrl",
2848+
Principal="*",
2849+
FunctionUrlAuthType=function_url_config["authorizer"],
2850+
)
2851+
2852+
@mock.patch("botocore.client")
2853+
def test_update_lambda_function_url(self, client):
2854+
boto_mock = mock.MagicMock()
2855+
zappa_core = Zappa(
2856+
boto_session=boto_mock,
2857+
profile_name="test",
2858+
aws_region="test",
2859+
load_credentials=True,
2860+
)
2861+
function_name = "abc"
2862+
function_arn = "arn:aws:lambda:ap-southeast-1:123456789:function:{}".format(function_name)
2863+
function_url_config = {
2864+
"authorizer": "NONE",
2865+
"cors": {
2866+
"allowedOrigins": ["*"],
2867+
"allowedHeaders": ["*"],
2868+
"allowedMethods": ["*"],
2869+
"allowCredentials": False,
2870+
"exposedResponseHeaders": ["*"],
2871+
"maxAge": 0,
2872+
},
2873+
}
2874+
zappa_core.lambda_client.list_function_url_configs.return_value = {
2875+
"ResponseMetadata": {
2876+
"HTTPStatusCode": 200,
2877+
},
2878+
"FunctionUrlConfigs": [
2879+
{
2880+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
2881+
"FunctionArn": function_arn,
2882+
}
2883+
],
2884+
}
2885+
zappa_core.lambda_client.update_function_url_config.return_value = {
2886+
"ResponseMetadata": {
2887+
"HTTPStatusCode": 200,
2888+
},
2889+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
2890+
"FunctionArn": function_arn,
2891+
}
2892+
zappa_core.lambda_client.get_policy.return_value = {
2893+
"ResponseMetadata": {
2894+
"HTTPStatusCode": 200,
2895+
},
2896+
"Policy": '{"Version":"2012-10-17","Id":"default","Statement":[{"Sid":"FunctionURLAllowPublicAccess","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunction","Resource":""}]}',
2897+
}
2898+
2899+
zappa_core.update_lambda_function_url(function_name="abc", function_url_config=function_url_config)
2900+
boto_mock.client().update_function_url_config.assert_called_with(
2901+
FunctionName=function_arn,
2902+
AuthType=function_url_config["authorizer"],
2903+
Cors={
2904+
"AllowCredentials": function_url_config["cors"]["allowCredentials"],
2905+
"AllowHeaders": function_url_config["cors"]["allowedHeaders"],
2906+
"AllowMethods": function_url_config["cors"]["allowedMethods"],
2907+
"AllowOrigins": function_url_config["cors"]["allowedOrigins"],
2908+
"ExposeHeaders": function_url_config["cors"]["exposedResponseHeaders"],
2909+
"MaxAge": function_url_config["cors"]["maxAge"],
2910+
},
2911+
)
2912+
2913+
boto_mock.client().get_policy.assert_called_with(
2914+
FunctionName=function_arn,
2915+
)
2916+
boto_mock.client().add_permission.assert_not_called()
2917+
boto_mock.client().create_function_url_config.assert_not_called()
2918+
2919+
@mock.patch("botocore.client")
2920+
def test_update_lambda_function_url_iam_authorizer(self, client):
2921+
boto_mock = mock.MagicMock()
2922+
zappa_core = Zappa(
2923+
boto_session=boto_mock,
2924+
profile_name="test",
2925+
aws_region="test",
2926+
load_credentials=True,
2927+
)
2928+
function_name = "abc"
2929+
function_arn = "arn:aws:lambda:ap-southeast-1:123456789:function:{}".format(function_name)
2930+
function_url_config = {
2931+
"authorizer": "AWS_IAM",
2932+
"cors": {
2933+
"allowedOrigins": ["*"],
2934+
"allowedHeaders": ["*"],
2935+
"allowedMethods": ["*"],
2936+
"allowCredentials": False,
2937+
"exposedResponseHeaders": ["*"],
2938+
"maxAge": 0,
2939+
},
2940+
}
2941+
zappa_core.lambda_client.list_function_url_configs.return_value = {
2942+
"ResponseMetadata": {
2943+
"HTTPStatusCode": 200,
2944+
},
2945+
"FunctionUrlConfigs": [
2946+
{
2947+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
2948+
"FunctionArn": function_arn,
2949+
}
2950+
],
2951+
}
2952+
zappa_core.lambda_client.update_function_url_config.return_value = {
2953+
"ResponseMetadata": {
2954+
"HTTPStatusCode": 200,
2955+
},
2956+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
2957+
"FunctionArn": function_arn,
2958+
}
2959+
zappa_core.lambda_client.get_policy.return_value = {
2960+
"ResponseMetadata": {
2961+
"HTTPStatusCode": 200,
2962+
},
2963+
"Policy": '{"Version":"2012-10-17","Id":"default","Statement":[{"Sid":"FunctionURLAllowPublicAccess","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunction","Resource":""}]}',
2964+
}
2965+
zappa_core.lambda_client.remove_permission.return_value = {
2966+
"ResponseMetadata": {"HTTPStatusCode": 200},
2967+
"Policy": '{"Version":"2012-10-17","Id":"default","Statement":[{"Sid":"FunctionURLAllowPublicAccess","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunction","Resource":"xxxxx"}]}',
2968+
}
2969+
zappa_core.update_lambda_function_url(function_name="abc", function_url_config=function_url_config)
2970+
boto_mock.client().update_function_url_config.assert_called_with(
2971+
FunctionName=function_arn,
2972+
AuthType=function_url_config["authorizer"],
2973+
Cors={
2974+
"AllowCredentials": function_url_config["cors"]["allowCredentials"],
2975+
"AllowHeaders": function_url_config["cors"]["allowedHeaders"],
2976+
"AllowMethods": function_url_config["cors"]["allowedMethods"],
2977+
"AllowOrigins": function_url_config["cors"]["allowedOrigins"],
2978+
"ExposeHeaders": function_url_config["cors"]["exposedResponseHeaders"],
2979+
"MaxAge": function_url_config["cors"]["maxAge"],
2980+
},
2981+
)
2982+
2983+
boto_mock.client().get_policy.assert_called_with(
2984+
FunctionName=function_arn,
2985+
)
2986+
boto_mock.client().delete_policy.remove_permission(
2987+
FunctionName=function_arn, StatementId="FunctionURLAllowPublicAccess"
2988+
)
2989+
boto_mock.client().add_permission.assert_not_called()
2990+
boto_mock.client().create_function_url_config.assert_not_called()
2991+
2992+
@mock.patch("botocore.client")
2993+
def test_delete_lambda_function_url(self, client):
2994+
boto_mock = mock.MagicMock()
2995+
zappa_core = Zappa(
2996+
boto_session=boto_mock,
2997+
profile_name="test",
2998+
aws_region="test",
2999+
load_credentials=True,
3000+
)
3001+
function_name = "abc"
3002+
function_arn = "arn:aws:lambda:ap-southeast-1:123456789:function:{}".format(function_name)
3003+
3004+
zappa_core.lambda_client.list_function_url_configs.return_value = {
3005+
"ResponseMetadata": {
3006+
"HTTPStatusCode": 200,
3007+
},
3008+
"FunctionUrlConfigs": [
3009+
{
3010+
"FunctionUrl": "https://123456789.lambda-url.ap-southeast-1.on.aws/",
3011+
"FunctionArn": function_arn,
3012+
}
3013+
],
3014+
}
3015+
zappa_core.lambda_client.delete_function_url_config.return_value = {
3016+
"ResponseMetadata": {
3017+
"HTTPStatusCode": 204,
3018+
}
3019+
}
3020+
zappa_core.lambda_client.get_policy.return_value = {
3021+
"ResponseMetadata": {
3022+
"HTTPStatusCode": 200,
3023+
},
3024+
"Policy": '{"Version":"2012-10-17","Id":"default","Statement":[{"Sid":"FunctionURLAllowPublicAccess","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunction","Resource":""}]}',
3025+
}
3026+
zappa_core.lambda_client.remove_permission.return_value = {
3027+
"ResponseMetadata": {
3028+
"HTTPStatusCode": 204,
3029+
"RetryAttempts": 0,
3030+
}
3031+
}
3032+
zappa_core.delete_lambda_function_url(function_name=function_arn)
3033+
boto_mock.client().delete_function_url_config.assert_called_with(
3034+
FunctionName=function_arn,
3035+
)
3036+
3037+
boto_mock.client().get_policy.assert_called_with(
3038+
FunctionName=function_arn,
3039+
)
3040+
boto_mock.client().delete_policy.remove_permission(
3041+
FunctionName=function_arn, StatementId="FunctionURLAllowPublicAccess"
3042+
)
3043+
boto_mock.client().add_permission.assert_not_called()
3044+
boto_mock.client().create_function_url_config.assert_not_called()
3045+
boto_mock.client().update_function_url_config.assert_not_called()
3046+
27853047
@mock.patch("sys.version_info", new_callable=get_sys_versioninfo)
27863048
def test_unsupported_version_error(self, *_):
27873049
from importlib import reload

0 commit comments

Comments
 (0)