Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for AWS Lambda Function SDK V2 #2130

Merged
merged 52 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
bb91ea5
Add Support for AWS Lambda Function SDK V2
driverpt May 19, 2024
ac22ee8
Make Spotless happy
driverpt May 30, 2024
51f58c1
Fix compilation issues
driverpt Jun 9, 2024
640f53c
Initial Commit for Tests
driverpt Jun 9, 2024
83fd2d2
Add Lambda Code for Testing with LocalStack
driverpt Jun 14, 2024
7458176
Add Integration Tests for Function Client for AWS SDK V2
driverpt Jun 14, 2024
054a4e6
Add missing Function Client Configuration
driverpt Jun 24, 2024
1755112
Update Javadoc
driverpt Jun 24, 2024
e11a6bf
Merge branch '4.7.x' into pr/2130
sdelamo Jul 1, 2024
1149df3
add javadoc, test and @Factory
sdelamo Jul 1, 2024
361a8e3
Update aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambd…
sdelamo Jul 1, 2024
c42b086
Update aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambd…
sdelamo Jul 1, 2024
b6f4639
Update function-client-aws-v2/src/main/java/io/micronaut/function/cli…
sdelamo Jul 1, 2024
592e3e6
Update function-client-aws-v2/src/main/java/io/micronaut/function/cli…
sdelamo Jul 1, 2024
5f03912
Update function-client-aws-v2/src/main/java/io/micronaut/function/cli…
sdelamo Jul 1, 2024
a79b204
Update function-client-aws-v2/src/main/java/io/micronaut/function/cli…
sdelamo Jul 1, 2024
9256c52
Use AwsInvokeRequestDefinition in javadoc
sdelamo Jul 1, 2024
38d17d6
document Lambda Client
sdelamo Jul 1, 2024
3e446f5
dependencies should be testImplementation
sdelamo Jul 1, 2024
7d6c3cf
remove testContainers version
sdelamo Jul 1, 2024
7545215
doc: how to use function-client-v2
sdelamo Jul 1, 2024
9d51b0f
use micronautFunctionClientAwsV2 in test-suite
sdelamo Jul 1, 2024
a704fde
add logback to src/test/resources
sdelamo Jul 1, 2024
b6de860
don’t use compress
sdelamo Jul 1, 2024
91c9b78
remove zip
sdelamo Jul 1, 2024
6fb0c0e
Fix lambda code
driverpt Jul 3, 2024
1b6d770
Add AWS SDK IAM to Tests
driverpt Jul 3, 2024
bec81a3
Remove @Internal from Lambda Factory
driverpt Jul 3, 2024
1b35302
Properly Map Lambda Function Name and extra attributes
driverpt Jul 3, 2024
e51547a
Fix failing tests
driverpt Jul 3, 2024
1af4c14
change javadoc
sdelamo Jul 4, 2024
fd01b82
revert v2 dependency
sdelamo Jul 4, 2024
cd94423
set AwsLambdaFunctionExecutor as @Internal
sdelamo Jul 4, 2024
3be9d9c
dont add jackson.databind use JsonMediaTypeCodec
sdelamo Jul 4, 2024
b8d9c51
move to package v2
sdelamo Jul 4, 2024
651614b
change javadoc
sdelamo Jul 4, 2024
421ae9d
add test
sdelamo Jul 4, 2024
764bd31
remove @Introspected annotation
sdelamo Jul 4, 2024
497a8e8
remove raw use of parameterized
sdelamo Jul 4, 2024
022ef66
use BLOCKING instead of IO
sdelamo Jul 4, 2024
560e00d
remove raw use parameterized
sdelamo Jul 4, 2024
5b00e50
use conversionService
sdelamo Jul 4, 2024
c54ed50
add reactive test
sdelamo Jul 4, 2024
a78e20b
fix: run Junit4 test into Junit5 test
sdelamo Jul 4, 2024
4799024
test-suite for function-client-aws v1 and v2
sdelamo Jul 4, 2024
95b58d8
disable binary compatibility
sdelamo Jul 4, 2024
615d2bc
improve docs
sdelamo Jul 4, 2024
9472988
add javadoc
sdelamo Jul 4, 2024
cf924e3
fix checkstyle
sdelamo Jul 4, 2024
3446aee
add javadoc
sdelamo Jul 4, 2024
07c5629
Merge branch '4.7.x' into pr/2130
sdelamo Aug 20, 2024
9c814af
disabled tests
sdelamo Aug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions aws-sdk-v2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
compileOnly(libs.awssdk.secretsmanager)
compileOnly(libs.awssdk.servicediscovery)
compileOnly(libs.awssdk.cloudwatchlogs)
compileOnly(libs.awssdk.lambda)

// Tests
testAnnotationProcessor(mn.micronaut.inject.java)
Expand All @@ -41,6 +42,7 @@ dependencies {
testImplementation(libs.awssdk.sqs)
testImplementation(libs.awssdk.ssm)
testImplementation(libs.awssdk.rekognition)
testImplementation(libs.awssdk.lambda)
testRuntimeOnly(libs.jcl.over.slf4j)

testRuntimeOnly(mn.snakeyaml)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.aws.sdk.v2.service.lambda;

import io.micronaut.aws.sdk.v2.service.AWSServiceConfiguration;
import io.micronaut.aws.sdk.v2.service.AwsClientFactory;
import io.micronaut.aws.ua.UserAgentProvider;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.regions.providers.AwsRegionProviderChain;
import software.amazon.awssdk.services.lambda.LambdaAsyncClient;
import software.amazon.awssdk.services.lambda.LambdaAsyncClientBuilder;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.LambdaClientBuilder;

/**
* Factory that creates {@link LambdaClient} and {@link LambdaAsyncClient}.
* @since 4.7.0
*/
@Factory
@Internal
class LambdaClientFactory extends AwsClientFactory<LambdaClientBuilder, LambdaAsyncClientBuilder, LambdaClient, LambdaAsyncClient> {
/**
* Constructor.
*
* @param credentialsProvider The credentials provider
* @param regionProvider The region provider
* @param userAgentProvider User-Agent Provider
* @param awsServiceConfiguration AWS Service Configuration
*/
protected LambdaClientFactory(AwsCredentialsProviderChain credentialsProvider,
AwsRegionProviderChain regionProvider,
@Nullable UserAgentProvider userAgentProvider,
@Nullable @Named(LambdaClient.SERVICE_NAME) AWSServiceConfiguration awsServiceConfiguration) {
super(credentialsProvider, regionProvider, userAgentProvider, awsServiceConfiguration);
}

@Override
protected LambdaClientBuilder createSyncBuilder() {
return LambdaClient.builder();
}

@Override
protected LambdaAsyncClientBuilder createAsyncBuilder() {
return LambdaAsyncClient.builder();
}

@Override
@Singleton
public LambdaClientBuilder syncBuilder(SdkHttpClient httpClient) {
return super.syncBuilder(httpClient);
}

@Override
@Bean(preDestroy = "close")
@Singleton
public LambdaClient syncClient(LambdaClientBuilder builder) {
return super.syncClient(builder);
}

@Override
@Singleton
@Requires(beans = SdkAsyncHttpClient.class)
public LambdaAsyncClientBuilder asyncBuilder(SdkAsyncHttpClient httpClient) {
return super.asyncBuilder(httpClient);
}

@Override
@Bean(preDestroy = "close")
@Singleton
@Requires(beans = SdkAsyncHttpClient.class)
public LambdaAsyncClient asyncClient(LambdaAsyncClientBuilder builder) {
return super.asyncClient(builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Lambda client factory.
* @author Luis Duarte
* @since 4.7.0
*/
@Requires(classes = {LambdaClient.class, LambdaAsyncClient.class})
@Configuration
package io.micronaut.aws.sdk.v2.service.lambda;

import io.micronaut.context.annotation.Configuration;
import io.micronaut.context.annotation.Requires;
import software.amazon.awssdk.services.lambda.LambdaAsyncClient;
import software.amazon.awssdk.services.lambda.LambdaClient;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.micronaut.aws.sdk.v2.service

import software.amazon.awssdk.services.lambda.LambdaAsyncClient
import software.amazon.awssdk.services.lambda.LambdaClient

class LambdaClientSpec extends ServiceClientSpec<LambdaClient, LambdaAsyncClient> {
@Override
protected String serviceName() {
return LambdaClient.SERVICE_NAME
}

@Override
protected LambdaClient getClient() {
applicationContext.getBean(LambdaClient)
}

protected LambdaAsyncClient getAsyncClient() {
applicationContext.getBean(LambdaAsyncClient)
}
}
25 changes: 25 additions & 0 deletions function-client-aws-v2/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id("io.micronaut.build.internal.aws-module")
}

dependencies {
api(projects.micronautAwsSdkV2)
implementation(libs.awssdk.lambda)
implementation(mn.reactor)
api(mn.micronaut.function.client)
implementation(mn.micronaut.jackson.databind)
testAnnotationProcessor(mn.micronaut.inject.java)
testImplementation(mn.micronaut.inject.java)
testImplementation(mnSerde.micronaut.serde.api)
testImplementation(mn.micronaut.http.server.netty)
testImplementation(mn.micronaut.function.web)
testImplementation(mnGroovy.micronaut.function.groovy)
testImplementation(mnGroovy.micronaut.runtime.groovy)
testImplementation(libs.commons.compress)
// TODO: Switch to Test Resources, once Lambda Client is also included in Service Override
implementation(platform(libs.boms.testcontainers))
implementation(libs.testcontainers)
implementation(libs.testcontainers.localstack)
implementation(libs.testcontainers.junit)
implementation(libs.testcontainers.spock)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.function.client.aws;

import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.function.client.FunctionDefinition;

/**
* Builds an {@link InvokeRequest} for each definition under {@code aws.lambda.functions}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the javadoc correct here?

*
* @author graemerocher
sdelamo marked this conversation as resolved.
Show resolved Hide resolved
* @since 1.0
sdelamo marked this conversation as resolved.
Show resolved Hide resolved
*/
@EachProperty(AwsInvokeRequestDefinition.AWS_LAMBDA_FUNCTIONS)
public class AwsInvokeRequestDefinition implements FunctionDefinition {
public static final String AWS_LAMBDA_FUNCTIONS = "aws.lambda.functions";

private final String name;

/**
* Constructor.
* @param name configured name from a property
*/
public AwsInvokeRequestDefinition(@Parameter String name) {
this.name = name;
}

@Override
public String getName() {
return this.name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.function.client.aws;

import io.micronaut.context.annotation.Requires;
import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.io.buffer.ByteBufferFactory;
import io.micronaut.core.type.Argument;
import io.micronaut.function.client.FunctionDefinition;
import io.micronaut.function.client.FunctionInvoker;
import io.micronaut.function.client.FunctionInvokerChooser;
import io.micronaut.function.client.exceptions.FunctionExecutionException;
import io.micronaut.jackson.codec.JsonMediaTypeCodec;
import io.micronaut.scheduling.TaskExecutors;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.lambda.LambdaAsyncClient;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;

import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.concurrent.ExecutorService;

/**
* A {@link FunctionInvoker} for invoking functions on AWS.
*
* @param <I> input type
* @param <O> output type
* @author graemerocher
* @since 1.0
*/
@Requires(beans = LambdaAsyncClient.class)
@Singleton
public class AwsLambdaFunctionExecutor<I, O> implements FunctionInvoker<I, O>, FunctionInvokerChooser {

private static final int STATUS_CODE_ERROR = 300;
private final LambdaClient syncClient;
private final LambdaAsyncClient asyncClient;
private final ByteBufferFactory byteBufferFactory;
private final JsonMediaTypeCodec jsonMediaTypeCodec;
private final ExecutorService ioExecutor;

/**
* Constructor.
*
* @param asyncClient asyncClient
* @param byteBufferFactory byteBufferFactory
* @param jsonMediaTypeCodec jsonMediaTypeCodec
* @param ioExecutor ioExecutor
*/
protected AwsLambdaFunctionExecutor(
LambdaClient syncClient,
LambdaAsyncClient asyncClient,
ByteBufferFactory byteBufferFactory,
JsonMediaTypeCodec jsonMediaTypeCodec,
@Named(TaskExecutors.IO) ExecutorService ioExecutor) {
this.syncClient = syncClient;
this.asyncClient = asyncClient;
this.byteBufferFactory = byteBufferFactory;
this.jsonMediaTypeCodec = jsonMediaTypeCodec;
this.ioExecutor = ioExecutor;
}

@Override
public O invoke(FunctionDefinition definition, I input, Argument<O> outputType) {
if (!(definition instanceof AwsInvokeRequestDefinition)) {
throw new IllegalArgumentException("Function definition must be a AWSInvokeRequestDefinition");
}

boolean isReactiveType = Publishers.isConvertibleToPublisher(outputType.getType());
SdkBytes sdkBytes = encodeInput(input);
InvokeRequest invokeRequest = InvokeRequest.builder()
.functionName(definition.getName())
.payload(sdkBytes)
.build();
if (isReactiveType) {
Mono<Object> invokeFlowable = Mono.fromFuture(asyncClient.invoke(invokeRequest))
.map(invokeResult ->
decodeResult(definition, (Argument<O>) outputType.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT), invokeResult))
.onErrorResume(throwable -> Mono.error(new FunctionExecutionException("Error executing AWS Lambda [" + definition.getName() + "]: " + throwable.getMessage(), throwable)))
.subscribeOn(Schedulers.fromExecutor(ioExecutor));
return ConversionService.SHARED.convert(invokeFlowable, outputType).orElseThrow(() -> new IllegalArgumentException("Unsupported Reactive type: " + outputType));
} else {
InvokeResponse invokeResult = syncClient.invoke(invokeRequest);
try {
return (O) decodeResult(definition, outputType, invokeResult);
} catch (Exception e) {
throw new FunctionExecutionException("Error executing AWS Lambda [" + definition.getName() + "]: " + e.getMessage(), e);
}
}
}

private Object decodeResult(FunctionDefinition definition, Argument<O> outputType, InvokeResponse invokeResult) {
Integer statusCode = invokeResult.statusCode();
if (statusCode >= STATUS_CODE_ERROR) {
throw new FunctionExecutionException("Error executing AWS Lambda [" + definition.getName() + "]: " + invokeResult.functionError());
}
io.micronaut.core.io.buffer.ByteBuffer byteBuffer = byteBufferFactory.copiedBuffer(invokeResult.payload().asByteArray());

return jsonMediaTypeCodec.decode(outputType, byteBuffer);
}

private SdkBytes encodeInput(I input) {
if (input != null) {
ByteBuffer nioBuffer = jsonMediaTypeCodec.encode(input, byteBufferFactory).asNioBuffer();
return SdkBytes.fromByteBuffer(nioBuffer);
}
return null;
}

@SuppressWarnings("unchecked")
@Override
public <I1, O2> Optional<FunctionInvoker<I1, O2>> choose(FunctionDefinition definition) {
if (definition instanceof AwsInvokeRequestDefinition) {
return Optional.of((FunctionInvoker) this);
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Function AWS lamda configuration.
*
* @author graemerocher
sdelamo marked this conversation as resolved.
Show resolved Hide resolved
* @since 1.0
sdelamo marked this conversation as resolved.
Show resolved Hide resolved
*/
package io.micronaut.function.client.aws;

Loading
Loading