From eb7e9afbb6e2af6cc5500bba8dcec54665d2871b Mon Sep 17 00:00:00 2001 From: sobrab <170367248+sobrab@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:00:22 +0200 Subject: [PATCH 1/4] added a Java example for an API Gateway with proxy resources to HTTP backend APIs --- java/http-proxy-apigateway/.gitignore | 13 ++ java/http-proxy-apigateway/README.md | 54 +++++ java/http-proxy-apigateway/cdk.json | 76 +++++++ java/http-proxy-apigateway/pom.xml | 62 +++++ .../com/myorg/HttpProxyApiGatewayApp.java | 29 +++ .../com/myorg/HttpProxyApiGatewayStack.java | 67 ++++++ .../com/myorg/HttpProxyApiGatewayTest.java | 213 ++++++++++++++++++ .../src/test/java/com/myorg/TestUtils.java | 80 +++++++ 8 files changed, 594 insertions(+) create mode 100644 java/http-proxy-apigateway/.gitignore create mode 100644 java/http-proxy-apigateway/README.md create mode 100644 java/http-proxy-apigateway/cdk.json create mode 100644 java/http-proxy-apigateway/pom.xml create mode 100644 java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java create mode 100644 java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java create mode 100644 java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java create mode 100644 java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java diff --git a/java/http-proxy-apigateway/.gitignore b/java/http-proxy-apigateway/.gitignore new file mode 100644 index 0000000000..1db21f1629 --- /dev/null +++ b/java/http-proxy-apigateway/.gitignore @@ -0,0 +1,13 @@ +.classpath.txt +target +.classpath +.project +.idea +.settings +.vscode +*.iml + +# CDK asset staging directory +.cdk.staging +cdk.out + diff --git a/java/http-proxy-apigateway/README.md b/java/http-proxy-apigateway/README.md new file mode 100644 index 0000000000..5a16df6a95 --- /dev/null +++ b/java/http-proxy-apigateway/README.md @@ -0,0 +1,54 @@ +# HTTP Proxy APIGateway + + + +--- +![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge) +> **This is a stable example. It should successfully build out of the box** +> +> This example is built on Construct Libraries marked "Stable" and does not have any infrastructure prerequisites to build. +--- + + + +This example creates an API Gateway with proxy resources for 2 HTTP backends. +More HTTP backend APIs can be easily added. +This is useful for scenarios when incoming requests must be routed to one or more backend API hosts. +An HTTP proxy integration enables direct interactions between clients and backends without any intervention from API Gateway after the API method is set up. + +> For more information on using HTTP proxy integrations with the APIGateway check out this [AWS tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html). + +> For demonstration purposes this CDK example deploys a solution that routes to the [PetsStore API](http://petstore-demo-endpoint.execute-api.com/) (from this [AWS tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html)) and to the [OpenTrivia API](https://opentdb.com). +> If you prefer to use your own HTTP backend APIs modify the argument used to call the [`HttpProxyApiGatewayStack`](src/main/java/com/myorg/HttpProxyApiGatewayStack.java) constructor in the [`HttpProxyApiGatewayApp`](src/main/java/com/myorg/HttpProxyApiGatewayApp.java) class. + +## Build + +To build this example, you need to be in this example's root directory. Then run the following: + +```bash + npm install -g aws-cdk + npm install + cdk synth +``` + +This will install the necessary CDK, then this example's dependencies, and then build the CloudFormation template. The resulting CloudFormation template will be in the `cdk.out` directory. + +## Deploy + +Run `cdk deploy`. +This will deploy / redeploy the Stack to AWS. +After the CDK deployment is successful, 2 URL examples will be available in the terminal console output: + +- One for the `PetStoreProxyEndPointGetRequestExample` stack output +- One for the `OpenTriviaProxyEndPointGetRequestExample` stack output + +At this point, you can copy each of the 2 URLs and paste them in the address bar of a browser to invoke the 2 APIs. + +## Useful commands + +* `mvn package` compile and run tests +* `cdk ls` list all stacks in the app +* `cdk synth` emits the synthesized CloudFormation template +* `cdk deploy` deploy this stack to your default AWS account/region +* `cdk diff` compare deployed stack with current state +* `cdk docs` open CDK documentation diff --git a/java/http-proxy-apigateway/cdk.json b/java/http-proxy-apigateway/cdk.json new file mode 100644 index 0000000000..1ce7118872 --- /dev/null +++ b/java/http-proxy-apigateway/cdk.json @@ -0,0 +1,76 @@ +{ + "app": "mvn -e -q compile exec:java", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTimeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true + } +} diff --git a/java/http-proxy-apigateway/pom.xml b/java/http-proxy-apigateway/pom.xml new file mode 100644 index 0000000000..90e17e056b --- /dev/null +++ b/java/http-proxy-apigateway/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + com.myorg + http-proxy-apigateway + 0.1 + + + UTF-8 + 2.167.1 + [10.0.0,11.0.0) + 5.7.1 + 22 + 22 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 22 + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + com.myorg.HttpProxyApiGatewayApp + + + + + + + + + software.amazon.awscdk + aws-cdk-lib + ${cdk.version} + + + + software.constructs + constructs + ${constructs.version} + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + diff --git a/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java new file mode 100644 index 0000000000..0d9cbe6901 --- /dev/null +++ b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java @@ -0,0 +1,29 @@ +package com.myorg; + +import software.amazon.awscdk.App; +import software.amazon.awscdk.StackProps; +import software.amazon.awscdk.services.apigatewayv2.HttpMethod; + +import java.util.List; + +import static com.myorg.HttpProxyApiGatewayStack.*; + +public class HttpProxyApiGatewayApp { + public static void main(final String[] args) { + var app = new App(); + new HttpProxyApiGatewayStack( + app, + "HttpProxyApiGatewayStack", + StackProps.builder() + .stackName("HttpProxyApiGatewayStack") + .build(), + List.of( + // Replace with resources corresponding to your own HTTP backend APIs if you want to. + // Add more resources if you need to. + new ProxyResourceParameters("PetStore", "http://petstore-demo-endpoint.execute-api.com", HttpMethod.ANY.name(), "/petstore/pets?type=fish"), + new ProxyResourceParameters("OpenTrivia", "https://opentdb.com", HttpMethod.ANY.name(), "/api.php?amount=10") + ) + ); + app.synth(); + } +} diff --git a/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java new file mode 100644 index 0000000000..986dc5d6bc --- /dev/null +++ b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java @@ -0,0 +1,67 @@ +package com.myorg; + +import software.amazon.awscdk.CfnOutput; +import software.amazon.awscdk.RemovalPolicy; +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.StackProps; +import software.amazon.awscdk.services.apigateway.*; +import software.constructs.Construct; + +import java.util.List; +import java.util.Map; + +public class HttpProxyApiGatewayStack extends Stack { + + public record ProxyResourceParameters(String resourceId, String baseUrl, String httpMethod, String exampleRequest) { + + } + + private final RestApi restApi; + + public HttpProxyApiGatewayStack(final Construct scope, final String id, final StackProps props, List proxyResourceParameters) { + super(scope, id, props); + restApi = RestApi.Builder.create(this, "RestApi") + .restApiName("RestApi") + .cloudWatchRole(true) + .cloudWatchRoleRemovalPolicy(RemovalPolicy.DESTROY) + .endpointTypes(List.of(EndpointType.EDGE)) + .build(); + proxyResourceParameters.forEach(this::createProxyResource); + } + + private void createProxyResource(ProxyResourceParameters proxyResourceParameters) { + var parentProxyResource = restApi.getRoot().addResource(proxyResourceParameters.resourceId); + var proxyResource = ProxyResource.Builder.create(this, proxyResourceParameters.resourceId + "ProxyResource") + .parent(parentProxyResource) + .anyMethod(false) + .build(); + var integrationOptions = IntegrationOptions.builder() + .requestParameters( + Map.of( + "integration.request.path.proxy", "method.request.path.proxy" + ) + ) + .build(); + var httpIntegrationProps = HttpIntegrationProps.builder() + .proxy(true) + .httpMethod(proxyResourceParameters.httpMethod) + .options(integrationOptions) + .build(); + var methodOptions = MethodOptions.builder() + .requestParameters( + Map.of( + "method.request.path.proxy", true + ) + ) + .build(); + var httpIntegration = new HttpIntegration(proxyResourceParameters.baseUrl + "/{proxy}", httpIntegrationProps); + proxyResource.addMethod(proxyResourceParameters.httpMethod, httpIntegration, methodOptions); + var proxyResourceUrl = restApi.urlForPath(proxyResource.getPath()); + CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyEndPointAnyRequestTemplate") + .value(proxyResourceUrl) + .build(); + CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyEndPointGetRequestExample") + .value(proxyResourceUrl.replace("/{proxy+}", proxyResourceParameters.exampleRequest)) + .build(); + } +} diff --git a/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java b/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java new file mode 100644 index 0000000000..731d6da045 --- /dev/null +++ b/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java @@ -0,0 +1,213 @@ +package com.myorg; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; +import software.amazon.awscdk.App; +import software.amazon.awscdk.StackProps; +import software.amazon.awscdk.cxapi.CloudFormationStackArtifact; +import software.amazon.awscdk.services.apigatewayv2.HttpMethod; + +import java.util.*; +import java.util.stream.Stream; + +import static com.myorg.HttpProxyApiGatewayStack.*; +import static com.myorg.TestUtils.*; + +public class HttpProxyApiGatewayTest { + + private static final Map stackResourcesMap = new HashMap<>(); + + @BeforeAll + public static void setUp() { + var app = App.Builder.create().build(); + var httpProxyApiGatewayStack = new HttpProxyApiGatewayStack( + app, + "HttpProxyApiGatewayStack", + StackProps.builder() + .stackName("HttpProxyApiGatewayStack") + .build(), + List.of( + new ProxyResourceParameters("PetStore", "http://petstore-demo-endpoint.execute-api.com", HttpMethod.ANY.name(), "/petstore/pets?type=fish"), + new ProxyResourceParameters("OpenTrivia", "https://opentdb.com", HttpMethod.ANY.name(), "/api.php?amount=10") + ) + ); + Optional.of(app) + .map(App::synth) + .flatMap( + cloudAssembly -> Optional.of(httpProxyApiGatewayStack) + .map(HttpProxyApiGatewayStack::getArtifactId) + .map(cloudAssembly::getStackArtifact) + .map(CloudFormationStackArtifact::getTemplate) + ).flatMap( + template -> Optional.of(new ObjectMapper()) + .map(objectMapper -> objectMapper.valueToTree(template)) + .map(jsonNode -> jsonNode.at("/Resources")) + .map(JsonNode::fields) + ).ifPresent(stackResourceIterator -> stackResourceIterator.forEachRemaining( + stackResourceEntry -> { + if (stackResourceEntry != null && + StringUtils.isNotBlank(stackResourceEntry.getKey()) && + stackResourceEntry.getValue() != null && + !stackResourceEntry.getValue().isEmpty() + ) { + stackResourcesMap.put(stackResourceEntry.getKey(), stackResourceEntry.getValue()); + } + }) + ); + } + + @Test + @DisplayName("Test if the expected REST API is present in the resources of the stack.") + public void testRestApi() { + var restApiMatchMap = Map.of( + "/Type", "AWS::ApiGateway::RestApi", + "/Properties/EndpointConfiguration/Types/0", "EDGE", + "/Properties/Name", "RestApi" + ); + Assertions.assertNotNull(findResource(stackResourcesMap, restApiMatchMap)); + } + + @Test + @DisplayName("Test if the expected IAM role for the REST API is present in the resources of the stack.") + public void testIamRole() { + var iamRoleMatchMap = Map.of( + "/Type", "AWS::IAM::Role", + "/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole", + "/Properties/AssumeRolePolicyDocument/Statement/0/Effect", "Allow", + "/Properties/AssumeRolePolicyDocument/Statement/0/Principal/Service", "apigateway.amazonaws.com", + "/Properties/AssumeRolePolicyDocument/Version", "2012-10-17", + "/Properties/ManagedPolicyArns/0/Fn::Join/0", "", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/0", "arn:", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/1/Ref", "AWS::Partition", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/2", ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", + "/DeletionPolicy", "Delete" + ); + Assertions.assertNotNull(findResource(stackResourcesMap, iamRoleMatchMap)); + } + + @Test + @DisplayName("Test if the expected API Gateway account is present in the resources of the stack.") + public void testApiGatewayAccount() { + var restApiMatchMap = Map.of( + "/Type", "AWS::ApiGateway::RestApi" + ); + var restApiId = findResourceId(stackResourcesMap, restApiMatchMap); + var iamRoleMatchMap = Map.of( + "/Type", "AWS::IAM::Role" + ); + var iamRoleId = findResourceId(stackResourcesMap, iamRoleMatchMap); + var apiGatewayAccountMatchMap = Map.of( + "/Type", "AWS::ApiGateway::Account", + "/Properties/CloudWatchRoleArn/Fn::GetAtt/0", iamRoleId, + "/Properties/CloudWatchRoleArn/Fn::GetAtt/1", "Arn", + "/DependsOn/0", restApiId, + "/UpdateReplacePolicy", "Delete", + "/DeletionPolicy", "Delete" + ); + Assertions.assertNotNull(findResource(stackResourcesMap, apiGatewayAccountMatchMap)); + } + + @Test + @DisplayName("Test if the expected API Gateway deployment is present in the resources of the stack.") + public void testApiGatewayDeployment() { + var restApiMatchMap = Map.of( + "/Type", "AWS::ApiGateway::RestApi" + ); + var restApiId = findResourceId(stackResourcesMap, restApiMatchMap); + var apiGatewayDeploymentMatchMap = Map.of( + "/Type", "AWS::ApiGateway::Deployment", + "/Properties/RestApiId/Ref", restApiId + ); + var apiGatewayDeployment = findResource(stackResourcesMap, apiGatewayDeploymentMatchMap); + Assertions.assertNotNull(apiGatewayDeployment); + var dependsOnActualIds = extractAsStringArray(apiGatewayDeployment, "/DependsOn"); + var dependsOnExpectedIds = Stream.of( + findResourcesIds(stackResourcesMap, Map.of("/Type", "AWS::ApiGateway::Resource")), + findResourcesIds(stackResourcesMap, Map.of("/Type", "AWS::ApiGateway::Method")) + ) + .flatMap(Set::stream) + .collect(HashSet::new, Set::add, Set::addAll); + Assertions.assertEquals(dependsOnExpectedIds, dependsOnActualIds); + } + + @Test + @DisplayName("Test if the expected API Gateway stage is present in the resources of the stack.") + public void testApiGatewayStage() { + var restApiMatchMap = Map.of( + "/Type", "AWS::ApiGateway::RestApi" + ); + var restApiId = findResourceId(stackResourcesMap, restApiMatchMap); + var apiGatewayDeploymentMatchMap = Map.of( + "/Type", "AWS::ApiGateway::Deployment" + ); + var apiGatewayDeploymentId = findResourceId(stackResourcesMap, apiGatewayDeploymentMatchMap); + var apiGatewayAccount = Map.of( + "/Type", "AWS::ApiGateway::Account" + ); + var apiGatewayAccountId = findResourceId(stackResourcesMap, apiGatewayAccount); + var stageMatchMap = Map.of( + "/Type", "AWS::ApiGateway::Stage", + "/Properties/RestApiId/Ref", restApiId, + "/Properties/DeploymentId/Ref", apiGatewayDeploymentId, + "/Properties/StageName", "prod", + "/DependsOn/0", apiGatewayAccountId + ); + Assertions.assertNotNull(findResource(stackResourcesMap, stageMatchMap)); + } + + @Test + @DisplayName("Test if the expected API Gateway resources are present in the resources of the stack.") + public void testApiGatewayResources() { + var restApiMatchMap = Map.of( + "/Type", "AWS::ApiGateway::RestApi" + ); + var restApiId = findResourceId(stackResourcesMap, restApiMatchMap); + var apiGatewayResourcesMatchMap = Map.of( + "/Type", "AWS::ApiGateway::Resource", + "/Properties/RestApiId/Ref", restApiId + ); + Long expectedApiGatewayResources = 4L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, apiGatewayResourcesMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(resourcesCont -> resourcesCont.equals(expectedApiGatewayResources)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected API Gateway methods are present in the resources of the stack.") + public void testApiGatewayMethods() { + var restApiMatchMap = Map.of( + "/Type", "AWS::ApiGateway::RestApi" + ); + var restApiId = findResourceId(stackResourcesMap, restApiMatchMap); + var apiGatewayMethodsMatchMap = Map.of( + "/Type", "AWS::ApiGateway::Method", + "/Properties/AuthorizationType", "NONE", + "/Properties/HttpMethod", "ANY", + "/Properties/Integration/IntegrationHttpMethod", "ANY", + "/Properties/Integration/RequestParameters/integration.request.path.proxy", "method.request.path.proxy", + "/Properties/Integration/Type", "HTTP_PROXY", + "/Properties/RequestParameters/method.request.path.proxy", "true", + "/Properties/RestApiId/Ref", restApiId + ); + Long expectedPermissionsCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, apiGatewayMethodsMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedPermissionsCount)) + .isPresent() + ); + } + +} diff --git a/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java b/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java new file mode 100644 index 0000000000..4dd130c267 --- /dev/null +++ b/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java @@ -0,0 +1,80 @@ +package com.myorg; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.*; +import java.util.Map.Entry; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static org.junit.platform.commons.util.StringUtils.isNotBlank; + +public class TestUtils { + + public static String findResourceId(Map stackResources, Map matchMap) { + return Optional.ofNullable(findResource(stackResources, matchMap)) + .map(Entry::getKey) + .orElse(null); + } + + public static Entry findResource(Map stackResources, Map matchMap) { + return stackResources.entrySet().stream() + .filter(createResourcePredicate(matchMap)) + .findAny() + .orElse(null); + } + + public static Set findResourcesIds(Map stackResources, Map matchMap) { + return Optional.ofNullable(findResources(stackResources, matchMap)) + .map(Map::keySet) + .orElse(null); + } + + public static Map findResources(Map stackResources, Map matchMap) { + return stackResources.entrySet().stream() + .filter(createResourcePredicate(matchMap)) + .collect(Collectors.toMap( + Entry::getKey, + Entry::getValue + )); + } + + public static Set extractAsStringArray(Entry stackResource, String jsonPath) { + return Optional.ofNullable(stackResource) + .map(Entry::getValue) + .flatMap( + resource -> Optional.ofNullable(jsonPath) + .map(resource::at) + .map(JsonNode::elements) + .map(iterator -> { + var set = new HashSet(); + iterator.forEachRemaining(item -> set.add(item.asText())); + return set; + }) + ).orElseGet(HashSet::new); + } + + public static Predicate> createResourcePredicate(String expectedJsonPath, String expectedJsonValue) { + return stackResourceEntry -> Optional.ofNullable(stackResourceEntry) + .map(Entry::getValue) + .flatMap( + resource -> Optional.ofNullable(expectedJsonPath) + .map(resource::at) + .map(JsonNode::asText) + ).filter(propertyValue -> propertyValue.equals(expectedJsonValue)) + .isPresent(); + } + + public static Predicate> createResourcePredicate(Map matchMap) { + return Optional.ofNullable(matchMap) + .map(Map::entrySet) + .flatMap( + matchEntrySet -> matchEntrySet.stream() + .filter(Objects::nonNull) + .filter(matchEntry -> isNotBlank(matchEntry.getKey())) + .filter(matchEntry -> isNotBlank(matchEntry.getValue())) + .map(matchEntry -> createResourcePredicate(matchEntry.getKey(), matchEntry.getValue())) + .reduce(Predicate::and) + ).orElseGet(() -> _ -> false); + } +} From b3c8732fda0d28a2cbbc5bf6ffa402bba02eb3d1 Mon Sep 17 00:00:00 2001 From: sobrab <170367248+sobrab@users.noreply.github.com> Date: Sun, 8 Dec 2024 12:20:24 +0200 Subject: [PATCH 2/4] some changes to make this example independent of preexisting external APIs --- java/http-proxy-apigateway/README.md | 16 +- java/http-proxy-apigateway/pom.xml | 2 +- .../com/myorg/HttpProxyApiGatewayApp.java | 22 +-- .../com/myorg/HttpProxyApiGatewayStack.java | 60 ++++++- .../src/main/resources/lambdas/ByeFunction.py | 7 + .../main/resources/lambdas/HelloFunction.py | 7 + .../com/myorg/HttpProxyApiGatewayTest.java | 155 ++++++++++++++++-- 7 files changed, 221 insertions(+), 48 deletions(-) create mode 100644 java/http-proxy-apigateway/src/main/resources/lambdas/ByeFunction.py create mode 100644 java/http-proxy-apigateway/src/main/resources/lambdas/HelloFunction.py diff --git a/java/http-proxy-apigateway/README.md b/java/http-proxy-apigateway/README.md index 5a16df6a95..d06a316ae8 100644 --- a/java/http-proxy-apigateway/README.md +++ b/java/http-proxy-apigateway/README.md @@ -12,14 +12,15 @@ This example creates an API Gateway with proxy resources for 2 HTTP backends. -More HTTP backend APIs can be easily added. This is useful for scenarios when incoming requests must be routed to one or more backend API hosts. -An HTTP proxy integration enables direct interactions between clients and backends without any intervention from API Gateway after the API method is set up. +An HTTP proxy integration enables direct interactions between clients and backends without any intervention from the API Gateway after the API method is set up. > For more information on using HTTP proxy integrations with the APIGateway check out this [AWS tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html). -> For demonstration purposes this CDK example deploys a solution that routes to the [PetsStore API](http://petstore-demo-endpoint.execute-api.com/) (from this [AWS tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html)) and to the [OpenTrivia API](https://opentdb.com). -> If you prefer to use your own HTTP backend APIs modify the argument used to call the [`HttpProxyApiGatewayStack`](src/main/java/com/myorg/HttpProxyApiGatewayStack.java) constructor in the [`HttpProxyApiGatewayApp`](src/main/java/com/myorg/HttpProxyApiGatewayApp.java) class. +> For demonstration purposes this CDK example deploys a solution that routes to a couple of test HTTP APIs. +> The 2 test HTTP APIs are implemented using lambdas exposed through function URLs. +> This example can be modified though, if you prefer to use your own HTTP backend APIs. +> To do that you can modify the `createHTTPTestAPIs` method in the [`HttpProxyApiGatewayStack`](src/main/java/com/myorg/HttpProxyApiGatewayStack.java) class to return a list of `ProxyResourceParameters` corresponding to your own resources. ## Build @@ -37,12 +38,13 @@ This will install the necessary CDK, then this example's dependencies, and then Run `cdk deploy`. This will deploy / redeploy the Stack to AWS. -After the CDK deployment is successful, 2 URL examples will be available in the terminal console output: +After the CDK deployment is successful, 2 URL examples will be available in the terminal console: -- One for the `PetStoreProxyEndPointGetRequestExample` stack output -- One for the `OpenTriviaProxyEndPointGetRequestExample` stack output +- One for the `HttpProxyApiGatewayStack.HelloFunctionResourceExample` output +- One for the `HttpProxyApiGatewayStack.ByeFunctionResourceExample` output At this point, you can copy each of the 2 URLs and paste them in the address bar of a browser to invoke the 2 APIs. +Also note that both URLs have the same host (the DNS of the new API Gateway created). ## Useful commands diff --git a/java/http-proxy-apigateway/pom.xml b/java/http-proxy-apigateway/pom.xml index 90e17e056b..ad9f96f840 100644 --- a/java/http-proxy-apigateway/pom.xml +++ b/java/http-proxy-apigateway/pom.xml @@ -9,7 +9,7 @@ UTF-8 - 2.167.1 + 2.171.1 [10.0.0,11.0.0) 5.7.1 22 diff --git a/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java index 0d9cbe6901..3c13b21c21 100644 --- a/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java +++ b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java @@ -2,28 +2,14 @@ import software.amazon.awscdk.App; import software.amazon.awscdk.StackProps; -import software.amazon.awscdk.services.apigatewayv2.HttpMethod; - -import java.util.List; - -import static com.myorg.HttpProxyApiGatewayStack.*; public class HttpProxyApiGatewayApp { public static void main(final String[] args) { var app = new App(); - new HttpProxyApiGatewayStack( - app, - "HttpProxyApiGatewayStack", - StackProps.builder() - .stackName("HttpProxyApiGatewayStack") - .build(), - List.of( - // Replace with resources corresponding to your own HTTP backend APIs if you want to. - // Add more resources if you need to. - new ProxyResourceParameters("PetStore", "http://petstore-demo-endpoint.execute-api.com", HttpMethod.ANY.name(), "/petstore/pets?type=fish"), - new ProxyResourceParameters("OpenTrivia", "https://opentdb.com", HttpMethod.ANY.name(), "/api.php?amount=10") - ) - ); + var stackProps = StackProps.builder() + .stackName("HttpProxyApiGatewayStack") + .build(); + new HttpProxyApiGatewayStack(app, "HttpProxyApiGatewayStack", stackProps); app.synth(); } } diff --git a/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java index 986dc5d6bc..3597280eee 100644 --- a/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java +++ b/java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java @@ -5,11 +5,19 @@ import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.services.apigateway.*; +import software.amazon.awscdk.services.lambda.*; import software.constructs.Construct; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import static software.amazon.awscdk.services.apigatewayv2.HttpMethod.ANY; +import static software.amazon.awscdk.services.lambda.Runtime.PYTHON_3_12; + public class HttpProxyApiGatewayStack extends Stack { public record ProxyResourceParameters(String resourceId, String baseUrl, String httpMethod, String exampleRequest) { @@ -18,7 +26,7 @@ public record ProxyResourceParameters(String resourceId, String baseUrl, String private final RestApi restApi; - public HttpProxyApiGatewayStack(final Construct scope, final String id, final StackProps props, List proxyResourceParameters) { + public HttpProxyApiGatewayStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); restApi = RestApi.Builder.create(this, "RestApi") .restApiName("RestApi") @@ -26,7 +34,41 @@ public HttpProxyApiGatewayStack(final Construct scope, final String id, final St .cloudWatchRoleRemovalPolicy(RemovalPolicy.DESTROY) .endpointTypes(List.of(EndpointType.EDGE)) .build(); - proxyResourceParameters.forEach(this::createProxyResource); + createHTTPTestAPIs().forEach(this::createProxyResource); + } + + private List createHTTPTestAPIs() { + return Map.of( + "HelloFunction", "/hello?from=AWS", + "ByeFunction", "/bye?from=AWS" + ).entrySet().stream() + .map(functionEntry -> { + var functionName = functionEntry.getKey(); + var parameters = functionEntry.getValue(); + var function = Function.Builder.create(this, functionName) + .functionName(functionName) + .code(InlineCode.fromInline(getInlineCode("src/main/resources/lambdas/" + functionName + ".py"))) + .handler("index.handler") + .runtime(PYTHON_3_12) + .build(); + var lambdaFunctionAlias = Alias.Builder.create(this, functionName + "ProdAlias") + .aliasName("Prod") + .version(function.getCurrentVersion()) + .build(); + var functionURL = FunctionUrl.Builder.create(this, functionName + "Url") + .function(lambdaFunctionAlias) + .authType(FunctionUrlAuthType.NONE) + .invokeMode(InvokeMode.BUFFERED) + .cors( + FunctionUrlCorsOptions.builder() + .allowedOrigins(List.of("*")) + .allowedMethods(List.of(HttpMethod.GET)) + .allowedHeaders(List.of("*")) + .build() + ) + .build(); + return new ProxyResourceParameters(functionName + "Resource", functionURL.getUrl(), ANY.name(), parameters); + }).collect(ArrayList::new, List::add, List::addAll); } private void createProxyResource(ProxyResourceParameters proxyResourceParameters) { @@ -54,14 +96,22 @@ private void createProxyResource(ProxyResourceParameters proxyResourceParameters ) ) .build(); - var httpIntegration = new HttpIntegration(proxyResourceParameters.baseUrl + "/{proxy}", httpIntegrationProps); + var httpIntegration = new HttpIntegration(proxyResourceParameters.baseUrl + "{proxy}", httpIntegrationProps); proxyResource.addMethod(proxyResourceParameters.httpMethod, httpIntegration, methodOptions); var proxyResourceUrl = restApi.urlForPath(proxyResource.getPath()); - CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyEndPointAnyRequestTemplate") + CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyUrl") .value(proxyResourceUrl) .build(); - CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyEndPointGetRequestExample") + CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "Example") .value(proxyResourceUrl.replace("/{proxy+}", proxyResourceParameters.exampleRequest)) .build(); } + + private String getInlineCode(String path) { + try { + return new String(Files.readAllBytes(Path.of(path))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/java/http-proxy-apigateway/src/main/resources/lambdas/ByeFunction.py b/java/http-proxy-apigateway/src/main/resources/lambdas/ByeFunction.py new file mode 100644 index 0000000000..b75e729d22 --- /dev/null +++ b/java/http-proxy-apigateway/src/main/resources/lambdas/ByeFunction.py @@ -0,0 +1,7 @@ +import json +def handler(event, context): + fromValue = event.get('queryStringParameters', {}).get('from', 'Lambda') + return { + 'statusCode': 200, + 'body': json.dumps('Bye from ' + fromValue + '!') + } diff --git a/java/http-proxy-apigateway/src/main/resources/lambdas/HelloFunction.py b/java/http-proxy-apigateway/src/main/resources/lambdas/HelloFunction.py new file mode 100644 index 0000000000..847668e69c --- /dev/null +++ b/java/http-proxy-apigateway/src/main/resources/lambdas/HelloFunction.py @@ -0,0 +1,7 @@ +import json +def handler(event, context): + fromValue = event.get('queryStringParameters', {}).get('from', 'Lambda') + return { + 'statusCode': 200, + 'body': json.dumps('Hello from ' + fromValue + '!') + } diff --git a/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java b/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java index 731d6da045..e1d06d774c 100644 --- a/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java +++ b/java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java @@ -10,12 +10,10 @@ import software.amazon.awscdk.App; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.cxapi.CloudFormationStackArtifact; -import software.amazon.awscdk.services.apigatewayv2.HttpMethod; import java.util.*; import java.util.stream.Stream; -import static com.myorg.HttpProxyApiGatewayStack.*; import static com.myorg.TestUtils.*; public class HttpProxyApiGatewayTest { @@ -25,17 +23,10 @@ public class HttpProxyApiGatewayTest { @BeforeAll public static void setUp() { var app = App.Builder.create().build(); - var httpProxyApiGatewayStack = new HttpProxyApiGatewayStack( - app, - "HttpProxyApiGatewayStack", - StackProps.builder() - .stackName("HttpProxyApiGatewayStack") - .build(), - List.of( - new ProxyResourceParameters("PetStore", "http://petstore-demo-endpoint.execute-api.com", HttpMethod.ANY.name(), "/petstore/pets?type=fish"), - new ProxyResourceParameters("OpenTrivia", "https://opentdb.com", HttpMethod.ANY.name(), "/api.php?amount=10") - ) - ); + var stackProps = StackProps.builder() + .stackName("HttpProxyApiGatewayStack") + .build(); + var httpProxyApiGatewayStack = new HttpProxyApiGatewayStack(app, "HttpProxyApiGatewayStack", stackProps); Optional.of(app) .map(App::synth) .flatMap( @@ -74,7 +65,7 @@ public void testRestApi() { @Test @DisplayName("Test if the expected IAM role for the REST API is present in the resources of the stack.") - public void testIamRole() { + public void testRestAPIIamRole() { var iamRoleMatchMap = Map.of( "/Type", "AWS::IAM::Role", "/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole", @@ -98,7 +89,16 @@ public void testApiGatewayAccount() { ); var restApiId = findResourceId(stackResourcesMap, restApiMatchMap); var iamRoleMatchMap = Map.of( - "/Type", "AWS::IAM::Role" + "/Type", "AWS::IAM::Role", + "/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole", + "/Properties/AssumeRolePolicyDocument/Statement/0/Effect", "Allow", + "/Properties/AssumeRolePolicyDocument/Statement/0/Principal/Service", "apigateway.amazonaws.com", + "/Properties/AssumeRolePolicyDocument/Version", "2012-10-17", + "/Properties/ManagedPolicyArns/0/Fn::Join/0", "", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/0", "arn:", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/1/Ref", "AWS::Partition", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/2", ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", + "/DeletionPolicy", "Delete" ); var iamRoleId = findResourceId(stackResourcesMap, iamRoleMatchMap); var apiGatewayAccountMatchMap = Map.of( @@ -199,13 +199,134 @@ public void testApiGatewayMethods() { "/Properties/RequestParameters/method.request.path.proxy", "true", "/Properties/RestApiId/Ref", restApiId ); - Long expectedPermissionsCount = 2L; + Long expectedApiGatewayMethodsCount = 2L; Assertions.assertTrue( Optional.ofNullable(findResources(stackResourcesMap, apiGatewayMethodsMatchMap)) .map(Map::entrySet) .map(Set::stream) .map(Stream::count) - .filter(methodsCont -> methodsCont.equals(expectedPermissionsCount)) + .filter(methodsCont -> methodsCont.equals(expectedApiGatewayMethodsCount)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected IAM roles for the test lambdas are present in the resources of the stack.") + public void testLambdaFunctionsIAMRoles() { + var lambdaFunctionsIAMRolesMatchMap = Map.of( + "/Type", "AWS::IAM::Role", + "/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole", + "/Properties/AssumeRolePolicyDocument/Statement/0/Effect", "Allow", + "/Properties/AssumeRolePolicyDocument/Statement/0/Principal/Service", "lambda.amazonaws.com", + "/Properties/AssumeRolePolicyDocument/Version", "2012-10-17", + "/Properties/ManagedPolicyArns/0/Fn::Join/0", "", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/0", "arn:", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/1/Ref", "AWS::Partition", + "/Properties/ManagedPolicyArns/0/Fn::Join/1/2", ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ); + Long expectedLambdaFunctionsIAMRolesCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, lambdaFunctionsIAMRolesMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedLambdaFunctionsIAMRolesCount)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected lambda functions are present in the resources of the stack.") + public void testLambdaFunctions() { + var lambdaFunctionsMatchMap = Map.of( + "/Type", "AWS::Lambda::Function", + "/Properties/Runtime", "python3.12" + ); + Long expectedLambdaFunctionsCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, lambdaFunctionsMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedLambdaFunctionsCount)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected lambda versions are present in the resources of the stack.") + public void testLambdaVersions() { + var lambdaVersionsMatchMap = Map.of( + "/Type", "AWS::Lambda::Version" + ); + Long expectedLambdaVersionsCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, lambdaVersionsMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedLambdaVersionsCount)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected lambda aliases are present in the resources of the stack.") + public void testLambdaAliases() { + var lambdaAliasesMatchMap = Map.of( + "/Type", "AWS::Lambda::Alias", + "/Properties/Name", "Prod" + ); + Long expectedLambdaAliasesCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, lambdaAliasesMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedLambdaAliasesCount)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected lambda permissions are present in the resources of the stack.") + public void testLambdaPermissions() { + var lambdaPermissionsMatchMap = Map.of( + "/Type", "AWS::Lambda::Permission", + "/Properties/Action", "lambda:InvokeFunctionUrl", + "/Properties/FunctionUrlAuthType", "NONE", + "/Properties/Principal", "*" + ); + Long expectedLambdaPermissionsCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, lambdaPermissionsMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedLambdaPermissionsCount)) + .isPresent() + ); + } + + @Test + @DisplayName("Test if the expected lambda function URLs are present in the resources of the stack.") + public void testLambdaFunctionURLs() { + var lambdaFunctionsURLsMatchMap = Map.of( + "/Type", "AWS::Lambda::Url", + "/Properties/AuthType", "NONE", + "/Properties/InvokeMode", "BUFFERED", + "/Properties/Qualifier", "Prod", + "/Properties/Cors/AllowHeaders/0", "*", + "/Properties/Cors/AllowMethods/0", "GET", + "/Properties/Cors/AllowOrigins/0", "*" + ); + Long expectedLambdaFunctionsURLsCount = 2L; + Assertions.assertTrue( + Optional.ofNullable(findResources(stackResourcesMap, lambdaFunctionsURLsMatchMap)) + .map(Map::entrySet) + .map(Set::stream) + .map(Stream::count) + .filter(methodsCont -> methodsCont.equals(expectedLambdaFunctionsURLsCount)) .isPresent() ); } From ac986ea142b39ca1aeb614207bfb8a5569057759 Mon Sep 17 00:00:00 2001 From: Michael Kaiser Date: Fri, 27 Dec 2024 14:18:59 -0600 Subject: [PATCH 3/4] Update pom.xml Supported release version --- java/http-proxy-apigateway/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/http-proxy-apigateway/pom.xml b/java/http-proxy-apigateway/pom.xml index ad9f96f840..ade7ee1e74 100644 --- a/java/http-proxy-apigateway/pom.xml +++ b/java/http-proxy-apigateway/pom.xml @@ -23,7 +23,7 @@ maven-compiler-plugin 3.11.0 - 22 + 17 From fa090a613dc884c64e0bee49e6c4742d81386299 Mon Sep 17 00:00:00 2001 From: Michael Kaiser Date: Fri, 27 Dec 2024 14:24:15 -0600 Subject: [PATCH 4/4] Update TestUtils.java Fix _ not being allowed --- .../src/test/java/com/myorg/TestUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java b/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java index 4dd130c267..dc945f3f79 100644 --- a/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java +++ b/java/http-proxy-apigateway/src/test/java/com/myorg/TestUtils.java @@ -75,6 +75,7 @@ public static Predicate> createResourcePredicate(Map isNotBlank(matchEntry.getValue())) .map(matchEntry -> createResourcePredicate(matchEntry.getKey(), matchEntry.getValue())) .reduce(Predicate::and) - ).orElseGet(() -> _ -> false); - } + ).orElseGet(() -> entry -> false); +} + }