Skip to content

Commit

Permalink
Merge pull request #363 from epam/develop
Browse files Browse the repository at this point in the history
1.11.6
  • Loading branch information
bohdan-onsha authored May 28, 2024
2 parents 1089a4d + 3278dae commit c5de577
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 118 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [1.11.6] - 2024-05-24
- Added support of custom authorizer names in Open API specification security schemes
- Fixed quietness of errors while deploying/updating API Gateway via OpenAPI specification
- Fixed API GW deletion when openapi specification is invalid
- Fixed issue with the command `generate meta api_gateway_resource`
- Fixed lambda function deployment fails in case of matching any resource name with prefix or/and suffix

# [1.11.5] - 2024-05-09
- Syndicate Java plugin patched to version 1.11.1 to exclude extra dependencies
- Fixed an error related to export OpenAPI specification in extended prefix mode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.demolayerurl.response.ErrorMessage;
import com.demolayerurl.response.HelloMessage;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.syndicate.deployment.annotations.lambda.LambdaHandler;
import com.syndicate.deployment.annotations.lambda.LambdaLayer;
import com.syndicate.deployment.annotations.lambda.LambdaUrlConfig;
Expand All @@ -32,10 +31,10 @@
import com.syndicate.deployment.model.RetentionSetting;
import com.syndicate.deployment.model.lambda.url.AuthType;
import com.syndicate.deployment.model.lambda.url.InvokeMode;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

@LambdaHandler(
lambdaName = "hello-lambda",
Expand All @@ -56,32 +55,75 @@
authType = AuthType.NONE,
invokeMode = InvokeMode.BUFFERED
)
public class HelloHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
public class HelloHandler implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {

private static final int SC_OK = 200;
private static final int SC_BAD_REQUEST = 400;
private final Gson gson = new Gson();
private static final int SC_NOT_FOUND = 404;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private final Map<String, String> responseHeaders = Map.of("Content-Type", "application/json");
private final Map<RouteKey, Function<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse>> routeHandlers = Map.of(
new RouteKey("GET", "/"), this::handleGetRoot,
new RouteKey("GET", "/hello"), this::handleGetHello
);

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, Context context) {
context.getLogger().log(apiGatewayProxyRequestEvent.toString());
Map<String, String> queryStringParameters = apiGatewayProxyRequestEvent.getQueryStringParameters();
try {
return new APIGatewayProxyResponseEvent()
.withStatusCode(SC_OK)
.withBody(gson.toJson(new HelloMessage(String.format("Hello, %s", getUserName(queryStringParameters)))));
} catch (IllegalArgumentException exception) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(SC_BAD_REQUEST)
.withBody(gson.toJson(new ErrorMessage(exception.getMessage())));
}
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent requestEvent, Context context) {
RouteKey routeKey = new RouteKey(getMethod(requestEvent), getPath(requestEvent));
return routeHandlers.getOrDefault(routeKey, this::notFoundResponse).apply(requestEvent);
}

private APIGatewayV2HTTPResponse handleGetRoot(APIGatewayV2HTTPEvent requestEvent) {
return buildResponse(SC_OK, Body.ok("Use the path /hello to get greetings message"));
}

private APIGatewayV2HTTPResponse handleGetHello(APIGatewayV2HTTPEvent requestEvent) {
return buildResponse(SC_OK, Body.ok("Hello%s".formatted(
Optional.ofNullable(requestEvent.getQueryStringParameters())
.map(this::getUserName)
.map(", %s"::formatted)
.orElse(" from lambda! Use the query string parameter 'name' to specify your name")
)));
}

private APIGatewayV2HTTPResponse notFoundResponse(APIGatewayV2HTTPEvent requestEvent) {
return buildResponse(SC_NOT_FOUND, Body.error("The resource with method %s and path %s is not found".formatted(
getMethod(requestEvent),
getPath(requestEvent)
)));
}

private APIGatewayV2HTTPResponse buildResponse(int statusCode, Object body) {
return APIGatewayV2HTTPResponse.builder()
.withStatusCode(statusCode)
.withHeaders(responseHeaders)
.withBody(gson.toJson(body))
.build();
}

private String getMethod(APIGatewayV2HTTPEvent requestEvent) {
return requestEvent.getRequestContext().getHttp().getMethod();
}

private String getPath(APIGatewayV2HTTPEvent requestEvent) {
return requestEvent.getRequestContext().getHttp().getPath();
}

private String getUserName(Map<String, String> queryStringParameters) {
return Optional.ofNullable(queryStringParameters)
.map(params -> params.get("user"))
.filter(StringUtils::isNotEmpty)
.orElseThrow(() -> new IllegalArgumentException("the query string parameter 'user' has not been specified"));
return queryStringParameters.get("name");
}


private record RouteKey(String method, String path) {
}

private record Body(String message, String error) {
static Body ok(String message) {
return new Body(message, null);
}

static Body error(String error) {
return new Body(null, error);
}
}

}

This file was deleted.

This file was deleted.

4 changes: 2 additions & 2 deletions examples/java/demo-layer-url/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
<properties>
<maven-shade-plugin.version>3.5.2</maven-shade-plugin.version>
<syndicate.java.plugin.version>1.11.1</syndicate.java.plugin.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<src.dir>jsrc/main/java</src.dir>
<resources.dir>jsrc/main/resources</resources.dir>
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

setup(
name='aws-syndicate',
version='1.11.5',
version='1.11.6',
packages=find_packages(),
include_package_data=True,
install_requires=[
Expand Down
38 changes: 16 additions & 22 deletions syndicate/connection/api_gateway_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,13 @@ def create_rest_api(self, api_name,

def create_openapi(self, openapi_context):
# Create a new API Gateway with the OpenAPI definition
try:
response = self.client.import_rest_api(
body=json.dumps(openapi_context),
failOnWarnings=False
)
api_id = response['id']
_LOG.debug(f"API Gateway created successfully with ID: {api_id}")
return api_id
except self.client.exceptions.ClientError as e:
_LOG.error(f"An error occurred: {e}")
return None
response = self.client.import_rest_api(
body=json.dumps(openapi_context),
failOnWarnings=False
)
api_id = response['id']
_LOG.debug(f"API Gateway created successfully with ID: {api_id}")
return api_id

def describe_openapi(self, api_id, stage_name):
try:
Expand All @@ -100,21 +96,19 @@ def describe_openapi(self, api_id, stage_name):
)
return response
except self.client.exceptions.NotFoundException:
_LOG.error(f"Not found api with id: {api_id}")
_LOG.warning(f"Not found api with id: {api_id} "
f"and stage name: {stage_name}")
return None

def update_openapi(self, api_id, openapi_context):
# Update the API Gateway with the OpenAPI definition
try:
self.client.put_rest_api(
restApiId=api_id,
mode='overwrite',
body=json.dumps(openapi_context),
failOnWarnings=False
)
_LOG.debug("API Gateway updated successfully.")
except self.client.exceptions.ClientError as e:
_LOG.error(f"An error occurred: {e}")
self.client.put_rest_api(
restApiId=api_id,
mode='overwrite',
body=json.dumps(openapi_context),
failOnWarnings=False
)
_LOG.debug("API Gateway updated successfully.")

def remove_api(self, api_id):
"""
Expand Down
8 changes: 4 additions & 4 deletions syndicate/core/build/meta_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ def _merge_api_gw_list_typed_configurations(initial_resource,
def _populate_s3_path_python_node(meta, bundle_name):
name = meta.get('name')
version = meta.get('version')
prefix = meta.get('prefix')
suffix = meta.get('suffix')
prefix = meta.pop('prefix', None)
suffix = meta.pop('suffix', None)
if not name or not version:
raise AssertionError('Lambda config must contain name and version. '
'Existing configuration'
Expand Down Expand Up @@ -457,8 +457,8 @@ def create_resource_json(project_path: str, bundle_name: str) -> dict[
def _resolve_names_in_meta(resources_dict, old_value, new_value):
if isinstance(resources_dict, dict):
for k, v in resources_dict.items():
# if k == old_value:
# resources_dict[new_value] = resources_dict.pop(k)
if k in ['prefix', 'suffix']:
continue
if isinstance(v, str) and old_value == v:
resources_dict[k] = v.replace(old_value, new_value)
elif isinstance(v, str) and old_value in v and v.startswith('arn'):
Expand Down
6 changes: 3 additions & 3 deletions syndicate/core/groups/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from syndicate.core.generators.lambda_function import PROJECT_PATH_PARAM
from syndicate.core.helper import OrderedGroup, OptionRequiredIf, \
validate_incompatible_options, validate_authorizer_name_option, \
verbose_option
verbose_option, validate_api_gw_path
from syndicate.core.helper import ValidRegionParamType
from syndicate.core.helper import check_bundle_bucket_name
from syndicate.core.helper import resolve_project_path, timeit
Expand Down Expand Up @@ -290,7 +290,7 @@ def api_gateway_authorizer(ctx, **kwargs):
@meta.command(name='api_gateway_resource')
@click.option('--api_name', required=True, type=str,
help="Api gateway name to add index to")
@click.option('--path', required=True, type=click.Path(readable=False),
@click.option('--path', required=True, callback=validate_api_gw_path,
help="Resource path to create")
@click.option('--enable_cors', type=bool,
help="Enables CORS on the resourcemethod. If not specified, sets"
Expand All @@ -310,7 +310,7 @@ def api_gateway_resource(ctx, **kwargs):
@meta.command(name='api_gateway_resource_method')
@click.option('--api_name', required=True, type=str,
help="Api gateway name to add index to")
@click.option('--path', required=True, type=click.Path(readable=False),
@click.option('--path', required=True, callback=validate_api_gw_path,
help="Resource path to create")
@click.option('--method', required=True,
type=click.Choice(['POST', 'GET', 'DELETE', 'PUT', 'HEAD',
Expand Down
19 changes: 19 additions & 0 deletions syndicate/core/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,25 @@ def validate_authorizer_name_option(ctx, param, value):
return value


def validate_api_gw_path(ctx, param, value):
pattern = (r'^/[a-zA-Z0-9-._~+]+(?:(?:/*)?[a-zA-Z0-9-._~]*\{?[a-zA-Z0-9-.'
r'_~]+\}?)*$')
_LOG.debug(f"The parameter '--{param.name}' value is '{value}'")
if os.name == 'nt' and Path(value).is_absolute():
raise BadParameter(
f"Your terminal resolves the parameter '--{param.name}' value as "
f"a filesystem path. Please pass the parameter '--{param.name}' "
f"value without starting slash ('/')")
if not value.startswith('/'):
value = '/' + value
if not re.match(pattern, value):
raise BadParameter(
f"A valid API gateway path must begin with a '/' and can contain "
f"alphanumeric characters, hyphens, periods, underscores or "
"dynamic parameters wrapped in '{}'")
return value


def set_debug_log_level(ctx, param, value):
if value:
loggers = [logging.getLogger(name) for name in
Expand Down
33 changes: 19 additions & 14 deletions syndicate/core/resources/api_gateway_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,11 +449,17 @@ def _update_api_gateway_openapi_from_meta(self, name, meta, context):

def _resolve_cup_ids(self, openapi_context):
_LOG.debug('Going to resolve Cognito User Pools ARNs')
authorizer = deep_get(openapi_context,
['components', 'securitySchemes',
'authorizer', 'x-amazon-apigateway-authorizer'])
security_schemes = \
openapi_context.get('components', {}).get('securitySchemes', {})

if authorizer:
authorizers = [
value['x-amazon-apigateway-authorizer']
for _, value in security_schemes.items()
if (value.get('x-amazon-apigateway-authtype') ==
_COGNITO_AUTHORIZER_TYPE.lower()
and 'x-amazon-apigateway-authorizer' in value)]

for authorizer in authorizers:
pools_names = provider_arns = None
if authorizer.get('type') == _COGNITO_AUTHORIZER_TYPE.lower():
pools_names = authorizer.get(X_SDCT_EXTENSION_KEY)
Expand Down Expand Up @@ -1005,16 +1011,15 @@ def _remove_api_gateway(self, arn, config):
api_id = config['description']['id']
stage_name = config["resource_meta"]["deploy_stage"]
openapi_context = self.describe_openapi(api_id, stage_name)
if not openapi_context:
return
api_lambdas_arns = self.extract_api_gateway_lambdas_arns(
openapi_context)
api_lambda_auth_arns = self.extract_api_gateway_lambda_auth_arns(
openapi_context)
self.remove_lambdas_permissions(
api_id,
{*api_lambdas_arns, *api_lambda_auth_arns}
)
if openapi_context:
api_lambdas_arns = self.extract_api_gateway_lambdas_arns(
openapi_context)
api_lambda_auth_arns = self.extract_api_gateway_lambda_auth_arns(
openapi_context)
self.remove_lambdas_permissions(
api_id,
{*api_lambdas_arns, *api_lambda_auth_arns}
)
try:
self.connection.remove_api(api_id)
_LOG.info(f'API Gateway {api_id} was removed.')
Expand Down

0 comments on commit c5de577

Please sign in to comment.