diff --git a/aws-sdk-v2/build.gradle.kts b/aws-sdk-v2/build.gradle.kts index 7ea6a13001..aacc958d98 100644 --- a/aws-sdk-v2/build.gradle.kts +++ b/aws-sdk-v2/build.gradle.kts @@ -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) @@ -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) diff --git a/aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambda/LambdaClientFactory.java b/aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambda/LambdaClientFactory.java new file mode 100644 index 0000000000..864ccce541 --- /dev/null +++ b/aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambda/LambdaClientFactory.java @@ -0,0 +1,94 @@ +/* + * 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.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 +class LambdaClientFactory extends AwsClientFactory { + /** + * 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); + } +} diff --git a/aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambda/package-info.java b/aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambda/package-info.java new file mode 100644 index 0000000000..5115d73a68 --- /dev/null +++ b/aws-sdk-v2/src/main/java/io/micronaut/aws/sdk/v2/service/lambda/package-info.java @@ -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; diff --git a/aws-sdk-v2/src/test/groovy/io/micronaut/aws/sdk/v2/service/LambdaClientSpec.groovy b/aws-sdk-v2/src/test/groovy/io/micronaut/aws/sdk/v2/service/LambdaClientSpec.groovy new file mode 100644 index 0000000000..7966a9c683 --- /dev/null +++ b/aws-sdk-v2/src/test/groovy/io/micronaut/aws/sdk/v2/service/LambdaClientSpec.groovy @@ -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 { + @Override + protected String serviceName() { + return LambdaClient.SERVICE_NAME + } + + @Override + protected LambdaClient getClient() { + applicationContext.getBean(LambdaClient) + } + + protected LambdaAsyncClient getAsyncClient() { + applicationContext.getBean(LambdaAsyncClient) + } +} diff --git a/function-client-aws-v2/build.gradle.kts b/function-client-aws-v2/build.gradle.kts new file mode 100644 index 0000000000..def348e4c6 --- /dev/null +++ b/function-client-aws-v2/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("io.micronaut.build.internal.aws-module") +} + +dependencies { + api(projects.micronautAwsSdkV2) + implementation(libs.awssdk.lambda) + implementation(mn.reactor) + api(mn.micronaut.function.client) + 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(platform(mnTestResources.boms.testcontainers)) + testImplementation(libs.testcontainers) + testImplementation(libs.testcontainers.localstack) + testImplementation(libs.testcontainers.spock) + testImplementation(libs.awssdk.iam) +} +micronautBuild { + // new module, so no binary check + binaryCompatibility { + enabled.set(false) + } +} diff --git a/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/AwsInvokeRequestDefinition.java b/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/AwsInvokeRequestDefinition.java new file mode 100644 index 0000000000..82b70fceb1 --- /dev/null +++ b/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/AwsInvokeRequestDefinition.java @@ -0,0 +1,103 @@ +/* + * 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.v2; + +import io.micronaut.context.annotation.EachProperty; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.function.client.FunctionDefinition; + +/** + * Builds an {@link AwsInvokeRequestDefinition} for each definition under {@code aws.lambda.functions}. + * + * @since 4.7.0 + */ +@EachProperty(AwsInvokeRequestDefinition.AWS_LAMBDA_FUNCTIONS) +public class AwsInvokeRequestDefinition implements FunctionDefinition { + /** + * Configuration prefix. + */ + public static final String AWS_LAMBDA_FUNCTIONS = "aws.lambda.functions"; + + private final String name; + + private String functionName; + + private String qualifier; + + private String clientContext; + + /** + * Constructor. + * + * @param name configured name from a property + */ + public AwsInvokeRequestDefinition(@Parameter String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + + /** + * + * @return The name or ARN of the Lambda function, version, or alias. + */ + public String getFunctionName() { + return functionName; + } + + /** + * + * @param functionName The name or ARN of the Lambda function, version, or alias. + */ + public void setFunctionName(String functionName) { + this.functionName = functionName; + } + + /** + * + * @return Specify a version or alias to invoke a published version of the function. + */ + public String getQualifier() { + return qualifier; + } + + /** + * {@see software.amazon.awssdk.services.lambda.model.InvokeRequest#clientContext}. + * @return Up to 3,583 bytes of base64-encoded data about the invoking client to pass to the function in the context object. + */ + public String getClientContext() { + return clientContext; + } + + /** + * {@see software.amazon.awssdk.services.lambda.model.InvokeRequest#qualifier}. + * @param qualifier Specify a version or alias to invoke a published version of the function. + */ + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + /** + * + * @param clientContext Up to 3,583 bytes of base64-encoded data about the invoking client to pass to the function in the context object. + */ + public void setClientContext(String clientContext) { + this.clientContext = clientContext; + } +} diff --git a/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/AwsLambdaFunctionExecutor.java b/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/AwsLambdaFunctionExecutor.java new file mode 100644 index 0000000000..9c3ce5b247 --- /dev/null +++ b/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/AwsLambdaFunctionExecutor.java @@ -0,0 +1,155 @@ +/* + * 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.v2; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.Internal; +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.json.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 input type + * @param output type + * @author graemerocher + * @since 1.0 + */ +@Requires(beans = LambdaAsyncClient.class) +@Singleton +@Internal +public class AwsLambdaFunctionExecutor implements FunctionInvoker, FunctionInvokerChooser { + + private static final int STATUS_CODE_ERROR = 300; + private final LambdaClient syncClient; + private final LambdaAsyncClient asyncClient; + private final ByteBufferFactory byteBufferFactory; + private final JsonMediaTypeCodec mediaTypeCodec; + private final ExecutorService executor; + private final ConversionService conversionService; + + /** + * Constructor. + * + * @param syncClient Lambda Sync Client + * @param asyncClient Lambda Async Client + * @param byteBufferFactory byteBufferFactory + * @param mediaTypeCodec JsonMediaTypeCodec + * @param executor blocking executor + * @param conversionService ConversionService + */ + protected AwsLambdaFunctionExecutor( + LambdaClient syncClient, + LambdaAsyncClient asyncClient, + ByteBufferFactory byteBufferFactory, + JsonMediaTypeCodec mediaTypeCodec, + @Named(TaskExecutors.BLOCKING) ExecutorService executor, + ConversionService conversionService) { + this.syncClient = syncClient; + this.asyncClient = asyncClient; + this.byteBufferFactory = byteBufferFactory; + this.mediaTypeCodec = mediaTypeCodec; + this.executor = executor; + this.conversionService = conversionService; + } + + @Override + public O invoke(FunctionDefinition definition, I input, Argument outputType) { + if (!(definition instanceof AwsInvokeRequestDefinition)) { + throw new IllegalArgumentException("Function definition must be a AWSInvokeRequestDefinition"); + } + + boolean isReactiveType = Publishers.isConvertibleToPublisher(outputType.getType()); + SdkBytes sdkBytes = encodeInput(input); + AwsInvokeRequestDefinition awsInvokeRequestDefinition = (AwsInvokeRequestDefinition) definition; + InvokeRequest invokeRequest = createInvokeRequest(awsInvokeRequestDefinition, sdkBytes); + + if (isReactiveType) { + Mono invokeFlowable = Mono.fromFuture(asyncClient.invoke(invokeRequest)) + .map(invokeResult -> + decodeResult(definition, (Argument) 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(executor)); + return conversionService.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 InvokeRequest createInvokeRequest(AwsInvokeRequestDefinition awsInvokeRequestDefinition, + SdkBytes sdkBytes) { + return InvokeRequest.builder() + .functionName(awsInvokeRequestDefinition.getFunctionName()) + .qualifier(awsInvokeRequestDefinition.getQualifier()) + .clientContext(awsInvokeRequestDefinition.getClientContext()) + .payload(sdkBytes) + .build(); + } + + private Object decodeResult(FunctionDefinition definition, Argument 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 mediaTypeCodec.decode(outputType, byteBuffer); + } + + private SdkBytes encodeInput(I input) { + if (input != null) { + ByteBuffer nioBuffer = mediaTypeCodec.encode(input, byteBufferFactory).asNioBuffer(); + return SdkBytes.fromByteBuffer(nioBuffer); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Optional> choose(FunctionDefinition definition) { + if (definition instanceof AwsInvokeRequestDefinition) { + return Optional.of((FunctionInvoker) this); + } + return Optional.empty(); + } +} diff --git a/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/package-info.java b/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/package-info.java new file mode 100644 index 0000000000..25270c3b6e --- /dev/null +++ b/function-client-aws-v2/src/main/java/io/micronaut/function/client/aws/v2/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +/** + * AWS Lambda Function Client related classes. + * + * @since 4.7.0 + */ +package io.micronaut.function.client.aws.v2; + diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/AwsInvokeRequestDefinitionSpec.groovy b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/AwsInvokeRequestDefinitionSpec.groovy new file mode 100644 index 0000000000..463cc03a70 --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/AwsInvokeRequestDefinitionSpec.groovy @@ -0,0 +1,32 @@ +package io.micronaut.function.client.aws.v2 + +import io.micronaut.context.annotation.Property +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@Property(name = "aws.lambda.functions.foo.function-name", value = "x-function-name") +@Property(name = "aws.lambda.functions.foo.qualifier", value = "x-qualifier") +@Property(name = "aws.lambda.functions.foo.client-context", value = "x-client-context") +@Property(name = "aws.lambda.functions.bar.function-name", value = "z-function-name") +@Property(name = "aws.lambda.functions.bar.qualifier", value = "z-qualifier") +@Property(name = "aws.lambda.functions.bar.client-context", value = "z-client-context") +@MicronautTest +class AwsInvokeRequestDefinitionSpec extends Specification { + + @Inject + List awsInvokeRequestDefinitions + + void "test aws invoke request"() { + expect: + awsInvokeRequestDefinitions.find { it.name == 'foo' }.name == 'foo' + awsInvokeRequestDefinitions.find { it.name == 'foo' }.functionName == "x-function-name" + awsInvokeRequestDefinitions.find { it.name == 'foo' }.qualifier == "x-qualifier" + awsInvokeRequestDefinitions.find { it.name == 'foo' }.clientContext == "x-client-context" + + awsInvokeRequestDefinitions.find { it.name == 'bar' }.name == 'bar' + awsInvokeRequestDefinitions.find { it.name == 'bar' }.functionName == "z-function-name" + awsInvokeRequestDefinitions.find { it.name == 'bar' }.qualifier == "z-qualifier" + awsInvokeRequestDefinitions.find { it.name == 'bar' }.clientContext == "z-client-context" + } +} diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/ComplexType.java b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/ComplexType.java new file mode 100644 index 0000000000..27b8f5dc1f --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/ComplexType.java @@ -0,0 +1,33 @@ +package io.micronaut.function.client.aws.v2; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public class ComplexType { + private int aNumber; + private String aString; + + public ComplexType() { + } + + public ComplexType(int aNumber, String aString) { + this.aNumber = aNumber; + this.aString = aString; + } + + public int getaNumber() { + return aNumber; + } + + public void setaNumber(int aNumber) { + this.aNumber = aNumber; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } +} diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClient.java b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClient.java new file mode 100644 index 0000000000..a2e5bde7a9 --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClient.java @@ -0,0 +1,12 @@ +package io.micronaut.function.client.aws.v2; + +import io.micronaut.function.client.FunctionClient; +import io.micronaut.http.annotation.Body; +import jakarta.inject.Named; + +@FunctionClient +public interface TestFunctionClient { + + @Named("test-function") + TestFunctionClientResponse invokeFunction(@Body TestFunctionClientRequest request); +} diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClientRequest.java b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClientRequest.java new file mode 100644 index 0000000000..0e8fc403f4 --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClientRequest.java @@ -0,0 +1,43 @@ +package io.micronaut.function.client.aws.v2; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public class TestFunctionClientRequest { + private int aNumber; + private String aString; + private ComplexType aObject; + + public TestFunctionClientRequest() { + } + + public TestFunctionClientRequest(int aNumber, String aString, ComplexType aObject) { + this.aNumber = aNumber; + this.aString = aString; + this.aObject = aObject; + } + + public int getaNumber() { + return aNumber; + } + + public void setaNumber(int aNumber) { + this.aNumber = aNumber; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } + + public ComplexType getaObject() { + return aObject; + } + + public void setaObject(ComplexType aObject) { + this.aObject = aObject; + } +} diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClientResponse.java b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClientResponse.java new file mode 100644 index 0000000000..2319714358 --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionClientResponse.java @@ -0,0 +1,49 @@ +package io.micronaut.function.client.aws.v2; + +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; + +@Serdeable +public class TestFunctionClientResponse { + private int aNumber; + private String aString; + private ComplexType aObject; + private List anArray; + + public TestFunctionClientResponse() { + + } + + public int getaNumber() { + return aNumber; + } + + public void setaNumber(int aNumber) { + this.aNumber = aNumber; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } + + public ComplexType getaObject() { + return aObject; + } + + public void setaObject(ComplexType aObject) { + this.aObject = aObject; + } + + public List getAnArray() { + return anArray; + } + + public void setAnArray(List anArray) { + this.anArray = anArray; + } +} diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionReactiveClient.java b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionReactiveClient.java new file mode 100644 index 0000000000..d61afb647b --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionReactiveClient.java @@ -0,0 +1,12 @@ +package io.micronaut.function.client.aws.v2; + +import io.micronaut.function.client.FunctionClient; +import io.micronaut.http.annotation.Body; +import jakarta.inject.Named; +import org.reactivestreams.Publisher; + +@FunctionClient +public interface TestFunctionReactiveClient { + @Named("test-function-reactive") + Publisher invokeFunctionReactive(@Body TestFunctionClientRequest request); +} diff --git a/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionSpec.groovy b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionSpec.groovy new file mode 100644 index 0000000000..03f8a4465d --- /dev/null +++ b/function-client-aws-v2/src/test/groovy/io/micronaut/function/client/aws/v2/TestFunctionSpec.groovy @@ -0,0 +1,245 @@ +package io.micronaut.function.client.aws.v2 + +import io.micronaut.core.io.ResourceLoader +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import io.micronaut.test.support.TestPropertyProvider +import jakarta.inject.Inject +import org.testcontainers.containers.localstack.LocalStackContainer +import org.testcontainers.spock.Testcontainers +import org.testcontainers.utility.DockerImageName +import reactor.core.publisher.Mono +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain +import software.amazon.awssdk.core.SdkBytes +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.iam.IamClient +import software.amazon.awssdk.services.iam.model.AttachRolePolicyRequest +import software.amazon.awssdk.services.iam.model.CreatePolicyRequest +import software.amazon.awssdk.services.iam.model.CreateRoleRequest +import software.amazon.awssdk.services.iam.model.GetPolicyRequest +import software.amazon.awssdk.services.iam.model.GetRoleRequest +import software.amazon.awssdk.services.iam.model.Role +import software.amazon.awssdk.services.iam.waiters.IamWaiter +import software.amazon.awssdk.services.lambda.LambdaClient +import software.amazon.awssdk.services.lambda.model.Architecture +import software.amazon.awssdk.services.lambda.model.CreateFunctionRequest +import software.amazon.awssdk.services.lambda.model.DeleteFunctionRequest +import software.amazon.awssdk.services.lambda.model.FunctionCode +import software.amazon.awssdk.services.lambda.model.GetFunctionConfigurationRequest +import software.amazon.awssdk.services.lambda.model.Runtime +import software.amazon.awssdk.services.lambda.model.GetFunctionRequest +import software.amazon.awssdk.services.lambda.model.LambdaRequest +import spock.lang.Shared +import spock.lang.Specification + +import java.nio.file.Files +import java.nio.file.Path +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.IAM +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.LAMBDA + +@Testcontainers +@MicronautTest +class TestFunctionSpec extends Specification implements TestPropertyProvider { + + private static final String FUNCTION_NAME = "TEST_FUNCTION_NAME" + + @Shared + private LocalStackContainer localStackContainer = new LocalStackContainer(DockerImageName + .parse("localstack/localstack:3.4.0")) + .withServices(IAM, LAMBDA) + + @Inject + @Shared + LambdaClient lambdaClient + + @Inject + @Shared + ResourceLoader resourceLoader + + @Override + Map getProperties() { + Map.of( + "aws.access-key-id", localStackContainer.getAccessKey(), + "aws.secret-key", localStackContainer.getSecretKey(), + "aws.region", localStackContainer.getRegion(), + "aws.services.lambda.endpoint-override", localStackContainer.getEndpointOverride(LAMBDA).toString() + ) as Map + } + + @Inject + TestFunctionClient functionClient + + @Inject + TestFunctionReactiveClient testFunctionReactiveClient + + def setupSpec() { + try { + lambdaClient.getFunction(GetFunctionRequest.builder() + .functionName(FUNCTION_NAME) + .build()) + } catch(Exception e) { + // Create if not exists + byte[] bytes = lambdaBytes(resourceLoader) + LambdaRequest lambdaRequest = createFunctionRequest(bytes) + if (lambdaRequest instanceof CreateFunctionRequest) { + def waiter = lambdaClient.waiter() + + def function = lambdaClient.createFunction((CreateFunctionRequest) lambdaRequest) + waiter.waitUntilFunctionExists(GetFunctionRequest.builder() + .functionName(function.functionName()) + .build()) + GetFunctionConfigurationRequest getFunctionConfigurationRequest = + GetFunctionConfigurationRequest.builder().functionName(function.functionName()).build() + waiter.waitUntilFunctionActive(getFunctionConfigurationRequest) + } + } + } + + def "can invoke a JS Lambda function with the an @FunctionClient"() { + given: + Integer aNumber = 1 + String aString = "someString" + + when: + TestFunctionClientResponse result = functionClient + .invokeFunction(new TestFunctionClientRequest(aNumber, aString, new ComplexType(aNumber, aString))) + + then: + result.aNumber == aNumber + result.aString == aString + result.aObject + result.aObject.aNumber == aNumber + result.aObject.aString == aString + result.anArray.size() == 1 + result.anArray[0].aNumber == aNumber + result.anArray[0].aString == aString + } + + def "can invoke a JS Lambda function with the an @FunctionClient wtih reactive types"() { + given: + Integer aNumber = 1 + String aString = "someString" + when: + TestFunctionClientResponse result = Mono.from(testFunctionReactiveClient.invokeFunctionReactive(new TestFunctionClientRequest(aNumber, aString, new ComplexType(aNumber, aString)))).block() + + then: + result.aNumber == aNumber + result.aString == aString + result.aObject + result.aObject.aNumber == aNumber + result.aObject.aString == aString + result.anArray.size() == 1 + result.anArray[0].aNumber == aNumber + result.anArray[0].aString == aString + } + + private byte[] lambdaBytes(ResourceLoader resourceLoader) { + try (InputStream inputStream = resourceLoader.getResourceAsStream("classpath:lambda/index.js").orElseThrow()) { + byte[] fileBytes = inputStream.readAllBytes() + Path tempFile = Files.createTempFile(FUNCTION_NAME, ".zip"); + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(tempFile))) { + ZipEntry zipEntry = new ZipEntry("index.js") + zos.putNextEntry(zipEntry) + zos.write(fileBytes) + zos.closeEntry() + } + return Files.readAllBytes(tempFile); + } + } + + private Role getLambdaRole() { + def iamClient = IamClient.builder() + .region(Region.of(localStackContainer.getRegion())) + .credentialsProvider(AwsCredentialsProviderChain.of( + () -> AwsBasicCredentials.create(localStackContainer.getAccessKey(), localStackContainer.getSecretKey()) + )) + .endpointOverride(localStackContainer.getEndpointOverride(IAM)) + .build() + def roleName = "lambda-role"; + try { + return iamClient.getRole(GetRoleRequest.builder() + .roleName(roleName) + .build()).role(); + } catch (final Exception e) { + // Create if not exists + IamWaiter iamWaiter = iamClient.waiter(); + + CreatePolicyRequest request = CreatePolicyRequest.builder() + .policyName("lambda-invoke-policy") + .policyDocument(""" + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "LambdaInvoke", + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": "*" + } + ] + } + """.stripIndent()) + .build(); + + def policy = iamClient.createPolicy(request) + iamWaiter.waitUntilPolicyExists(GetPolicyRequest.builder() + .policyArn(policy.policy().arn()) + .build()); + + def role = iamClient.createRole(CreateRoleRequest.builder() + .roleName(roleName) + .path("/") + .assumeRolePolicyDocument(""" + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + } + """.stripIndent()) + .build()) + + iamWaiter.waitUntilRoleExists(GetRoleRequest.builder() + .roleName(role.role().roleName()) + .build()) + + iamClient.attachRolePolicy(AttachRolePolicyRequest.builder() + .roleName(role.role().roleName()) + .policyArn(policy.policy().arn()) + .build()) + + return role.role(); + } + } + + private LambdaRequest createFunctionRequest(byte[] arr) { + def role = getLambdaRole() + CreateFunctionRequest.builder() + .functionName(FUNCTION_NAME) + .role(role.arn()) + .code(FunctionCode.builder() + .zipFile(SdkBytes.fromByteArray(arr)) + .build()) + .runtime(Runtime.NODEJS18_X) + .architectures(Architecture.X86_64) + .handler("index.handler") + .build() + } + + private LambdaRequest deleteFunctionRequest() { + DeleteFunctionRequest.builder() + .functionName(FUNCTION_NAME) + .build() + } +} diff --git a/function-client-aws-v2/src/test/resources/application-test.properties b/function-client-aws-v2/src/test/resources/application-test.properties new file mode 100644 index 0000000000..cdaf3278ec --- /dev/null +++ b/function-client-aws-v2/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +aws.lambda.functions.test-function.function-name=TEST_FUNCTION_NAME +aws.lambda.functions.test-function-reactive.function-name=TEST_FUNCTION_NAME diff --git a/function-client-aws-v2/src/test/resources/lambda/index.js b/function-client-aws-v2/src/test/resources/lambda/index.js new file mode 100644 index 0000000000..a9c8b20423 --- /dev/null +++ b/function-client-aws-v2/src/test/resources/lambda/index.js @@ -0,0 +1,17 @@ +exports.handler = async (event, context) => { + if (!event.aNumber || !event.aString || !event.aObject || !event.aObject.aNumber || !event.aObject.aString) { + throw new Error('Invalid Input'); + } + + const arr = []; + arr.push(event.aObject); + + const response = { + aNumber: event.aNumber, + aString: event.aString, + aObject: event.aObject, + anArray: arr, + }; + + return response +}; diff --git a/function-client-aws-v2/src/test/resources/logback.xml b/function-client-aws-v2/src/test/resources/logback.xml new file mode 100644 index 0000000000..80dcc40c8d --- /dev/null +++ b/function-client-aws-v2/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/function-client-aws/build.gradle.kts b/function-client-aws/build.gradle.kts index 244c91d7e2..8f332dc738 100644 --- a/function-client-aws/build.gradle.kts +++ b/function-client-aws/build.gradle.kts @@ -14,4 +14,6 @@ dependencies { testImplementation(mn.micronaut.function.web) testImplementation(mnGroovy.micronaut.function.groovy) testImplementation(mnGroovy.micronaut.runtime.groovy) + testImplementation(mnTest.micronaut.test.junit5) + testRuntimeOnly(libs.junit.jupiter.engine) } diff --git a/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/AwsLambdaInvokeSpec.groovy b/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/AwsLambdaInvokeSpec.groovy index f3c7112f6c..3e4ad30705 100644 --- a/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/AwsLambdaInvokeSpec.groovy +++ b/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/AwsLambdaInvokeSpec.groovy @@ -73,7 +73,7 @@ class AwsLambdaInvokeSpec extends Specification { void "test setup lambda config"() { given: ApplicationContext applicationContext = ApplicationContext.run( - 'aws.lambda.functions.test.functionName':'micronaut-function', + 'aws.lambda.functions.test.function-name':'micronaut-function', 'aws.lambda.functions.test.qualifier':'something', 'aws.lambda.region':'us-east-1' ) @@ -94,7 +94,7 @@ class AwsLambdaInvokeSpec extends Specification { void "test invoke function"() { given: ApplicationContext applicationContext = ApplicationContext.run( - 'aws.lambda.functions.test.functionName':'micronaut-function', + 'aws.lambda.functions.test.function-name':'micronaut-function', 'aws.lambda.region':'us-east-1' ) @@ -123,7 +123,7 @@ class AwsLambdaInvokeSpec extends Specification { void "test invoke client with @FunctionClient"() { given: ApplicationContext applicationContext = ApplicationContext.run( - 'aws.lambda.functions.test.functionName':'micronaut-function', + 'aws.lambda.functions.test.function-name':'micronaut-function', 'aws.lambda.region':'us-east-1' ) diff --git a/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/LocalFunctionInvokeJavaSpec.java b/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/LocalFunctionInvokeJavaSpec.java deleted file mode 100644 index 7e3dbb7bcc..0000000000 --- a/function-client-aws/src/test/groovy/io/micronaut/function/client/aws/LocalFunctionInvokeJavaSpec.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.micronaut.function.client.aws; - -//tag::import[] -import io.micronaut.context.ApplicationContext; -import io.micronaut.function.client.FunctionClient; -import jakarta.inject.Named; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -//end::rxImport[] -//end::import[] - -import io.micronaut.runtime.server.EmbeddedServer; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -//tag::rxImport[] - -public class LocalFunctionInvokeJavaSpec { - - //tag::invokeLocalFunction[] - @Test - public void testInvokingALocalFunction() { - Sum sum = new Sum(); - sum.setA(5); - sum.setB(10); - - EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class); - MathClient mathClient = server.getApplicationContext().getBean(MathClient.class); - - assertEquals(Long.valueOf(Integer.MAX_VALUE), mathClient.max()); - assertEquals(2, mathClient.rnd(1.6f)); - assertEquals(15, mathClient.sum(sum)); - - } - //end::invokeLocalFunction[] - - //tag::invokeRxLocalFunction[] - @Test - public void testInvokingALocalFunctionRX() { - Sum sum = new Sum(); - sum.setA(5); - sum.setB(10); - - EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class); - ReactiveMathClient mathClient = server.getApplicationContext().getBean(ReactiveMathClient.class); - - assertEquals(Long.valueOf(Integer.MAX_VALUE), mathClient.max().block()); - assertEquals(2, mathClient.rnd(1.6f).block().longValue()); - assertEquals(15, mathClient.sum(sum).block().longValue()); - - } - //end::invokeRxLocalFunction[] - - //tag::beginFunctionClient[] - @FunctionClient - interface MathClient { - //end::beginFunctionClient[] - - //tag::functionMax[] - Long max(); //<1> - //end::functionMax[] - - //tag::functionRnd[] - @Named("round") - int rnd(float value); - //end::functionRnd[] - - long sum(Sum sum); - //tag::endFunctionClient[] - } - //end::endFunctionClient[] - - - //tag::rxFunctionClient[] - @FunctionClient - interface ReactiveMathClient { - Mono max(); - - @Named("round") - Mono rnd(float value); - - Mono sum(Sum sum); - } - //end::rxFunctionClient[] -} diff --git a/function-client-aws/src/test/java/io/micronaut/function/client/aws/LocalFunctionInvokeJavaTest.java b/function-client-aws/src/test/java/io/micronaut/function/client/aws/LocalFunctionInvokeJavaTest.java new file mode 100644 index 0000000000..6d927c824a --- /dev/null +++ b/function-client-aws/src/test/java/io/micronaut/function/client/aws/LocalFunctionInvokeJavaTest.java @@ -0,0 +1,46 @@ +package io.micronaut.function.client.aws; + +import io.micronaut.context.ApplicationContext; +import static org.junit.Assert.assertEquals; +import io.micronaut.runtime.server.EmbeddedServer; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import spock.lang.Ignore; + +class LocalFunctionInvokeJavaTest { + + @Disabled("it is flaky https://ge.micronaut.io/scans/tests?tests.container=io.micronaut.function.client.aws.LocalFunctionInvokeSpec") + @Test + void testInvokingALocalFunction() { + Suma sum = new Suma(); + sum.setA(5); + sum.setB(10); + + EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class); + MathClient mathClient = server.getApplicationContext().getBean(MathClient.class); + + assertEquals(Long.valueOf(Integer.MAX_VALUE), mathClient.max()); + assertEquals(2, mathClient.rnd(1.6f)); + assertEquals(15, mathClient.sum(sum)); + + server.close(); + } + + @Disabled("it is flaky https://ge.micronaut.io/scans/tests?tests.container=io.micronaut.function.client.aws.LocalFunctionInvokeSpec") + @Test + void testInvokingALocalFunctionRX() { + Suma sum = new Suma(); + sum.setA(5); + sum.setB(10); + + EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class); + ReactiveMathClient mathClient = server.getApplicationContext().getBean(ReactiveMathClient.class); + + assertEquals(Long.valueOf(Integer.MAX_VALUE), Mono.from(mathClient.max()).block()); + assertEquals(2, Mono.from(mathClient.rnd(1.6f)).block().longValue()); + assertEquals(15, Mono.from(mathClient.sum(sum)).block().longValue()); + + server.close(); + } +} diff --git a/function-client-aws/src/test/java/io/micronaut/function/client/aws/MathClient.java b/function-client-aws/src/test/java/io/micronaut/function/client/aws/MathClient.java new file mode 100644 index 0000000000..5a29aca59c --- /dev/null +++ b/function-client-aws/src/test/java/io/micronaut/function/client/aws/MathClient.java @@ -0,0 +1,14 @@ +package io.micronaut.function.client.aws; + +import io.micronaut.function.client.FunctionClient; +import jakarta.inject.Named; + +@FunctionClient +interface MathClient { + Long max(); + + @Named("round") + int rnd(float value); + + long sum(Suma sum); +} diff --git a/function-client-aws/src/test/java/io/micronaut/function/client/aws/ReactiveMathClient.java b/function-client-aws/src/test/java/io/micronaut/function/client/aws/ReactiveMathClient.java new file mode 100644 index 0000000000..890b3260e7 --- /dev/null +++ b/function-client-aws/src/test/java/io/micronaut/function/client/aws/ReactiveMathClient.java @@ -0,0 +1,15 @@ +package io.micronaut.function.client.aws; + +import io.micronaut.function.client.FunctionClient; +import jakarta.inject.Named; +import org.reactivestreams.Publisher; + +@FunctionClient +interface ReactiveMathClient { + Publisher max(); + + @Named("round") + Publisher rnd(float value); + + Publisher sum(Suma sum); +} diff --git a/function-client-aws/src/test/java/io/micronaut/function/client/aws/Suma.java b/function-client-aws/src/test/java/io/micronaut/function/client/aws/Suma.java new file mode 100644 index 0000000000..d8cb82cac2 --- /dev/null +++ b/function-client-aws/src/test/java/io/micronaut/function/client/aws/Suma.java @@ -0,0 +1,27 @@ +package io.micronaut.function.client.aws; + +/** + * @author graemerocher + * @since 1.0 + */ +public class Suma { + + private int a; + private Integer b; + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public Integer getB() { + return b; + } + + public void setB(Integer b) { + this.b = b; + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 986be1587a..de45e5dbab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,6 +69,7 @@ awssdk-apache-client = { module = 'software.amazon.awssdk:apache-client' } awssdk-apigatewaymanagementapi = { module = 'software.amazon.awssdk:apigatewaymanagementapi' } awssdk-cloudwatchlogs = { module = 'software.amazon.awssdk:cloudwatchlogs'} awssdk-dynamodb = { module = 'software.amazon.awssdk:dynamodb' } +awssdk-lambda = { module = 'software.amazon.awssdk:lambda' } awssdk-netty-nio-client = { module = 'software.amazon.awssdk:netty-nio-client' } awssdk-rekognition = { module = 'software.amazon.awssdk:rekognition' } awssdk-s3 = { module = 'software.amazon.awssdk:s3' } @@ -78,6 +79,7 @@ awssdk-ses = { module = 'software.amazon.awssdk:ses' } awssdk-sns = { module = 'software.amazon.awssdk:sns' } awssdk-sqs = { module = 'software.amazon.awssdk:sqs' } awssdk-ssm = { module = 'software.amazon.awssdk:ssm' } +awssdk-iam = { module = 'software.amazon.awssdk:iam' } awssdk-url-connection-client = { module = 'software.amazon.awssdk:url-connection-client' } kotlin-stdlib-jdk8 = { module = 'org.jetbrains.kotlin:kotlin-stdlib-jdk8', version.ref = 'kotlin' } @@ -118,3 +120,8 @@ servlet-api = { module = 'javax.servlet:servlet-api', version.ref = 'servlet-api gradle-micronaut = { module = "io.micronaut.gradle:micronaut-gradle-plugin", version.ref = "micronaut-gradle-plugin" } gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } + +testcontainers = { module = "org.testcontainers:testcontainers" } +testcontainers-localstack = { module = "org.testcontainers:localstack" } +testcontainers-junit = { module = "org.testcontainers:junit-jupiter" } +testcontainers-spock = { module = "org.testcontainers:spock" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 05e234f503..8fbdda62c8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,17 +37,21 @@ include("function-aws-api-proxy-test") include("function-aws-custom-runtime") include("function-aws-test") include("function-client-aws") +include("function-client-aws-v2") +include("test-suite-function-client-aws") include("test-suite") include("test-suite-aws-sdk-v2") include("test-suite-graal") include("test-suite-graal-logging") include("test-suite-groovy") +include("test-suite-function-client-aws-groovy") include("test-suite-http-server-tck-function-aws-api-gateway-proxy-alb") include("test-suite-http-server-tck-function-aws-api-gateway-proxy-payloadv1") include("test-suite-http-server-tck-function-aws-api-gateway-proxy-payloadv2") include("test-suite-http-server-tck-function-aws-api-proxy-test") include("test-suite-kotlin") +include("test-suite-function-client-aws-kotlin") include("test-suite-s3") configure { diff --git a/src/main/docs/guide/lambda/lambdafunctionclient.adoc b/src/main/docs/guide/lambda/lambdafunctionclient.adoc index 3f4cf86828..65b63f0af5 100644 --- a/src/main/docs/guide/lambda/lambdafunctionclient.adoc +++ b/src/main/docs/guide/lambda/lambdafunctionclient.adoc @@ -1,11 +1,19 @@ Micronaut AWS provides support for invoking AWS Lambda functions within a Micronaut application context. -To use the features described in this section, you will need to have the `micronaut-function-client-aws` dependency on your classpath. -dependency:micronaut-function-client-aws[groupId="io.micronaut.aws"] +=== AWS SDK V2 + +To use the features described in this section, you will need to have the following dependency on your classpath: + +dependency:micronaut-function-client-aws-v2[groupId="io.micronaut.aws"] + +NOTE: To invoke a function Micronaut configures a `LambdaAsyncClient` and `LambdaClient`. You can configure them by registering a https://docs.micronaut.io/latest/api/io/micronaut/context/event/BeanCreatedEventListener.html[BeanCreatedEventListener] for `software.amazon.awssdk.services.lambda.LambdaAsyncClient` or `software.amazon.awssdk.services.lambda.LambdaAsyncClientBuilder` You can define multiple named functions under the `aws.lambda.functions` configuration. -Each is configured by `AWSInvokeRequestDefinition` that allows setting any property on the underlying `com.amazonaws.services.lambda.model.InvokeRequest`. +Each is configured by `AwsInvokeRequestDefinition` that allows setting any property on the underlying `software.amazon.awssdk.services.lambda.model.InvokeRequest`. + + +=== Example For example, you invoke a function named `AwsLambdaFunctionName`, in the AWS Lambda console, with the following configuration: @@ -24,6 +32,16 @@ Alternatively, you can remove the `@Named` annotation and match the method name snippet::io.micronaut.docs.function.client.aws.methodnamed.AnalyticsClient[tags="clazz"] -To configure credentials for invoking the function you can either define a `~/.aws/credentials` file or use the application configuration file. Micronaut registers a api:configurations.aws.EnvironmentAWSCredentialsProvider[] that resolves AWS credentials from the Micronaut Environment. + +=== AWS SDK V1 + +To use AWS SDK v1 add the following dependency instead: + +dependency:micronaut-function-client-aws[groupId="io.micronaut.aws"] NOTE: To invoke a function Micronaut configures a `AWSLambdaAsyncClient` using api:function.client.aws.AWSLambdaConfiguration[] that allows configuring any of the properties of the `AWSLambdaAsyncClientBuilder` class. + +You can define multiple named functions under the `aws.lambda.functions` configuration. +Each is configured by `AWSInvokeRequestDefinition` that allows setting any property on the underlying `com.amazonaws.services.lambda.model.InvokeRequest`. + +To configure credentials for invoking the function you can either define a `~/.aws/credentials` file or use the application configuration file. Micronaut registers a api:configurations.aws.EnvironmentAWSCredentialsProvider[] that resolves AWS credentials from the Micronaut Environment. diff --git a/src/main/docs/guide/sdkv2/lambdaClient.adoc b/src/main/docs/guide/sdkv2/lambdaClient.adoc new file mode 100644 index 0000000000..495de2782e --- /dev/null +++ b/src/main/docs/guide/sdkv2/lambdaClient.adoc @@ -0,0 +1,15 @@ +To use a Lambda client, add the following dependency: + +dependency:lambda[groupId="software.amazon.awssdk"] + +Then, the following beans will be created: + +* `software.amazon.awssdk.services.lambda.LambdaClientBuilder` +* `software.amazon.awssdk.services.lambda.LambdaClient`. + +And: + +* `software.amazon.awssdk.services.lambda.LambdaAsyncClientBuilder` +* `software.amazon.awssdk.services.lambda.LambdaAsyncClient`. + +The HTTP client, credentials and region will be configured as per described in the <>. diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index a9789fe4e4..f9edf387a0 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -28,6 +28,7 @@ sdkv2: s3: S3 dynamodb: Dynamo DB ses: SES + lambdaClient: Lambda Client sns: SNS sqs: SQS ssm: SSM diff --git a/test-suite-function-client-aws-groovy/build.gradle.kts b/test-suite-function-client-aws-groovy/build.gradle.kts new file mode 100644 index 0000000000..ef95a4b33e --- /dev/null +++ b/test-suite-function-client-aws-groovy/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("groovy") + id("java-library") + id("io.micronaut.build.internal.aws-tests") +} + +dependencies { + testCompileOnly(mn.micronaut.inject.groovy) + testImplementation(mnTest.micronaut.test.spock) + testImplementation(platform(mn.micronaut.core.bom)) + testImplementation(projects.micronautFunctionClientAws) +} + +tasks { + named("test", Test::class) { + useJUnitPlatform() + } +} + +java { + sourceCompatibility = JavaVersion.toVersion("17") + targetCompatibility = JavaVersion.toVersion("17") +} diff --git a/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy b/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy new file mode 100644 index 0000000000..00770b9725 --- /dev/null +++ b/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy @@ -0,0 +1,30 @@ +package io.micronaut.docs.function.client.aws + +import io.micronaut.context.ApplicationContext +import io.micronaut.function.client.FunctionDefinition +import io.micronaut.function.client.aws.AWSInvokeRequestDefinition +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import spock.lang.Specification +import jakarta.inject.Inject + +@MicronautTest(startApplication = false) +class AnalyticsClientSpec extends Specification { + @Inject + ApplicationContext applicationContext + + void "test setup function definitions"() { + given: + Collection definitions = applicationContext.getBeansOfType(FunctionDefinition) + + expect: + definitions.size() == 1 + definitions.first() instanceof AWSInvokeRequestDefinition + + when: + AWSInvokeRequestDefinition invokeRequestDefinition = (AWSInvokeRequestDefinition) definitions.first() + + then: + invokeRequestDefinition.name == 'analytics' + invokeRequestDefinition.invokeRequest.functionName == 'AwsLambdaFunctionName' + } +} diff --git a/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.groovy b/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.groovy new file mode 100644 index 0000000000..f49d482ab8 --- /dev/null +++ b/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.groovy @@ -0,0 +1,9 @@ +package io.micronaut.docs.function.client.aws.atnamed + +import io.micronaut.function.client.FunctionClient +import jakarta.inject.Named +@FunctionClient +interface AnalyticsClient { + @Named('analytics') + String visit(String productId); +} diff --git a/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.groovy b/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.groovy new file mode 100644 index 0000000000..6dfdf74d6c --- /dev/null +++ b/test-suite-function-client-aws-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.groovy @@ -0,0 +1,8 @@ +package io.micronaut.docs.function.client.aws.methodnamed + +import io.micronaut.function.client.FunctionClient + +@FunctionClient +interface AnalyticsClient { + String analytics(String productId) +} diff --git a/test-suite-function-client-aws-groovy/src/test/resources/application.properties b/test-suite-function-client-aws-groovy/src/test/resources/application.properties new file mode 100644 index 0000000000..73c92aa9d4 --- /dev/null +++ b/test-suite-function-client-aws-groovy/src/test/resources/application.properties @@ -0,0 +1 @@ +aws.lambda.functions.analytics.function-name=AwsLambdaFunctionName \ No newline at end of file diff --git a/test-suite-function-client-aws-groovy/src/test/resources/logback.xml b/test-suite-function-client-aws-groovy/src/test/resources/logback.xml new file mode 100644 index 0000000000..80dcc40c8d --- /dev/null +++ b/test-suite-function-client-aws-groovy/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/test-suite-function-client-aws-kotlin/build.gradle.kts b/test-suite-function-client-aws-kotlin/build.gradle.kts new file mode 100644 index 0000000000..c423ba56a2 --- /dev/null +++ b/test-suite-function-client-aws-kotlin/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlin.kapt") + id("io.micronaut.build.internal.aws-tests") +} + +val micronautVersion: String by project + +dependencies { + kaptTest(mn.micronaut.inject.java) + testAnnotationProcessor(platform(mn.micronaut.core.bom)) + testImplementation(libs.junit.jupiter.api) + testImplementation(mnTest.micronaut.test.junit5) + testRuntimeOnly(libs.junit.jupiter.engine) + testImplementation(projects.micronautFunctionAws) + testImplementation(libs.kotlin.stdlib.jdk8) + testImplementation(projects.micronautFunctionClientAws) + testRuntimeOnly(mn.snakeyaml) +} + +tasks { + named("test", Test::class) { + useJUnitPlatform() + } +} + +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } + +} diff --git a/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt b/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt new file mode 100644 index 0000000000..1539ed0c88 --- /dev/null +++ b/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt @@ -0,0 +1,25 @@ +package io.micronaut.docs.function.aws + +import io.micronaut.context.ApplicationContext +import io.micronaut.function.client.FunctionDefinition +import io.micronaut.function.client.aws.AWSInvokeRequestDefinition +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import jakarta.inject.Inject +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +@MicronautTest(startApplication = false) +internal class AnalyticsClientTest { + @Inject + lateinit var applicationContext: ApplicationContext + @Test + fun testSetupFunctionDefinitions() { + val definitions = applicationContext.getBeansOfType(FunctionDefinition::class.java) + Assertions.assertEquals(1, definitions.size) + Assertions.assertTrue(definitions.stream().findFirst().isPresent) + Assertions.assertTrue(definitions.stream().findFirst().get() is AWSInvokeRequestDefinition) + val invokeRequestDefinition = definitions.stream().findFirst().get() as AWSInvokeRequestDefinition + Assertions.assertEquals("analytics", invokeRequestDefinition.name) + //Assertions.assertEquals("AwsLambdaFunctionName", invokeRequestDefinition.invokeRequest.functionName) + } +} \ No newline at end of file diff --git a/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.kt b/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.kt new file mode 100644 index 0000000000..f4ad54bfb1 --- /dev/null +++ b/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.kt @@ -0,0 +1,10 @@ +package io.micronaut.docs.function.client.aws.atnamed + +import io.micronaut.function.client.FunctionClient +import jakarta.inject.Named + +@FunctionClient +internal interface AnalyticsClient { + @Named("analytics") + fun visit(productId: String): String +} diff --git a/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.kt b/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.kt new file mode 100644 index 0000000000..27efa775e2 --- /dev/null +++ b/test-suite-function-client-aws-kotlin/src/test/kotlin/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.kt @@ -0,0 +1,8 @@ +package io.micronaut.docs.function.client.aws.methodnamed + +import io.micronaut.function.client.FunctionClient + +@FunctionClient +internal interface AnalyticsClient { + fun analytics(productId: String): String +} diff --git a/test-suite-function-client-aws-kotlin/src/test/resources/application.properties b/test-suite-function-client-aws-kotlin/src/test/resources/application.properties new file mode 100644 index 0000000000..73c92aa9d4 --- /dev/null +++ b/test-suite-function-client-aws-kotlin/src/test/resources/application.properties @@ -0,0 +1 @@ +aws.lambda.functions.analytics.function-name=AwsLambdaFunctionName \ No newline at end of file diff --git a/test-suite-function-client-aws-kotlin/src/test/resources/logback.xml b/test-suite-function-client-aws-kotlin/src/test/resources/logback.xml new file mode 100644 index 0000000000..80dcc40c8d --- /dev/null +++ b/test-suite-function-client-aws-kotlin/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/test-suite-function-client-aws/build.gradle.kts b/test-suite-function-client-aws/build.gradle.kts new file mode 100644 index 0000000000..49070ce16b --- /dev/null +++ b/test-suite-function-client-aws/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("java-library") + id("io.micronaut.build.internal.aws-tests-java") + id("io.micronaut.build.internal.common") +} +dependencies { + testImplementation(projects.micronautFunctionClientAws) +} + diff --git a/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java b/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java new file mode 100644 index 0000000000..60be703853 --- /dev/null +++ b/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java @@ -0,0 +1,32 @@ +package io.micronaut.docs.function.aws; + +import io.micronaut.context.ApplicationContext; +import io.micronaut.function.client.FunctionDefinition; +import io.micronaut.function.client.aws.AWSInvokeRequestDefinition; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest(startApplication = false) +class AnalyticsClientTest { + @Inject + ApplicationContext applicationContext; + + @Test + void testSetupFunctionDefinitions() { + Collection definitions = applicationContext.getBeansOfType(FunctionDefinition.class); + + assertEquals(1, definitions.size()); + assertTrue(definitions.stream().findFirst().isPresent()); + assertTrue(definitions.stream().findFirst().get() instanceof AWSInvokeRequestDefinition); + + AWSInvokeRequestDefinition invokeRequestDefinition = (AWSInvokeRequestDefinition) definitions.stream().findFirst().get(); + + assertEquals("analytics", invokeRequestDefinition.getName()); + } +} diff --git a/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.java b/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.java new file mode 100644 index 0000000000..0547f2dc2d --- /dev/null +++ b/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/client/aws/atnamed/AnalyticsClient.java @@ -0,0 +1,10 @@ +package io.micronaut.docs.function.client.aws.atnamed; + +import io.micronaut.function.client.FunctionClient; +import jakarta.inject.Named; +@FunctionClient +public interface AnalyticsClient { + + @Named("analytics") // <1> + String visit(String productId); +} diff --git a/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.java b/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.java new file mode 100644 index 0000000000..613d1c6e8a --- /dev/null +++ b/test-suite-function-client-aws/src/test/java/io/micronaut/docs/function/client/aws/methodnamed/AnalyticsClient.java @@ -0,0 +1,8 @@ +package io.micronaut.docs.function.client.aws.methodnamed; + +import io.micronaut.function.client.FunctionClient; + +@FunctionClient +public interface AnalyticsClient { + String analytics(String productId); +} diff --git a/test-suite-function-client-aws/src/test/resources/application.yml b/test-suite-function-client-aws/src/test/resources/application.yml new file mode 100644 index 0000000000..85c4f2e020 --- /dev/null +++ b/test-suite-function-client-aws/src/test/resources/application.yml @@ -0,0 +1,8 @@ +#tag::config[] +--- +aws: + lambda: + functions: + analytics: + function-name: 'AwsLambdaFunctionName' +#end::config[] \ No newline at end of file diff --git a/test-suite-function-client-aws/src/test/resources/logback.xml b/test-suite-function-client-aws/src/test/resources/logback.xml new file mode 100644 index 0000000000..80dcc40c8d --- /dev/null +++ b/test-suite-function-client-aws/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/test-suite-groovy/build.gradle.kts b/test-suite-groovy/build.gradle.kts index 5d263f217b..5d496ab412 100644 --- a/test-suite-groovy/build.gradle.kts +++ b/test-suite-groovy/build.gradle.kts @@ -9,7 +9,7 @@ dependencies { testImplementation(mnTest.micronaut.test.spock) testImplementation(platform(mn.micronaut.core.bom)) testImplementation(projects.micronautFunctionAws) - testImplementation(projects.micronautFunctionClientAws) + testImplementation(projects.micronautFunctionClientAwsV2) testRuntimeOnly(mn.snakeyaml) } diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy index 00770b9725..efedeaa7cd 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/function/client/aws/AnalyticsClientSpec.groovy @@ -2,7 +2,7 @@ package io.micronaut.docs.function.client.aws import io.micronaut.context.ApplicationContext import io.micronaut.function.client.FunctionDefinition -import io.micronaut.function.client.aws.AWSInvokeRequestDefinition +import io.micronaut.function.client.aws.v2.AwsInvokeRequestDefinition import io.micronaut.test.extensions.spock.annotation.MicronautTest import spock.lang.Specification import jakarta.inject.Inject @@ -18,13 +18,12 @@ class AnalyticsClientSpec extends Specification { expect: definitions.size() == 1 - definitions.first() instanceof AWSInvokeRequestDefinition + definitions.first() instanceof AwsInvokeRequestDefinition when: - AWSInvokeRequestDefinition invokeRequestDefinition = (AWSInvokeRequestDefinition) definitions.first() + AwsInvokeRequestDefinition invokeRequestDefinition = (AwsInvokeRequestDefinition) definitions.first() then: invokeRequestDefinition.name == 'analytics' - invokeRequestDefinition.invokeRequest.functionName == 'AwsLambdaFunctionName' } } diff --git a/test-suite-kotlin/build.gradle.kts b/test-suite-kotlin/build.gradle.kts index c423ba56a2..175c4cbe43 100644 --- a/test-suite-kotlin/build.gradle.kts +++ b/test-suite-kotlin/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { testRuntimeOnly(libs.junit.jupiter.engine) testImplementation(projects.micronautFunctionAws) testImplementation(libs.kotlin.stdlib.jdk8) - testImplementation(projects.micronautFunctionClientAws) + testImplementation(projects.micronautFunctionClientAwsV2) testRuntimeOnly(mn.snakeyaml) } diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt index 1539ed0c88..5418a00b27 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/function/aws/AnalyticsClientTest.kt @@ -2,7 +2,7 @@ package io.micronaut.docs.function.aws import io.micronaut.context.ApplicationContext import io.micronaut.function.client.FunctionDefinition -import io.micronaut.function.client.aws.AWSInvokeRequestDefinition +import io.micronaut.function.client.aws.v2.AwsInvokeRequestDefinition import io.micronaut.test.extensions.junit5.annotation.MicronautTest import jakarta.inject.Inject import org.junit.jupiter.api.Assertions @@ -17,8 +17,8 @@ internal class AnalyticsClientTest { val definitions = applicationContext.getBeansOfType(FunctionDefinition::class.java) Assertions.assertEquals(1, definitions.size) Assertions.assertTrue(definitions.stream().findFirst().isPresent) - Assertions.assertTrue(definitions.stream().findFirst().get() is AWSInvokeRequestDefinition) - val invokeRequestDefinition = definitions.stream().findFirst().get() as AWSInvokeRequestDefinition + Assertions.assertTrue(definitions.stream().findFirst().get() is AwsInvokeRequestDefinition) + val invokeRequestDefinition = definitions.stream().findFirst().get() as AwsInvokeRequestDefinition Assertions.assertEquals("analytics", invokeRequestDefinition.name) //Assertions.assertEquals("AwsLambdaFunctionName", invokeRequestDefinition.invokeRequest.functionName) } diff --git a/test-suite/build.gradle.kts b/test-suite/build.gradle.kts index f6339f4ffa..98872291df 100644 --- a/test-suite/build.gradle.kts +++ b/test-suite/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { testImplementation(projects.micronautFunctionAws) - testImplementation(projects.micronautFunctionClientAws) + testImplementation(projects.micronautFunctionClientAwsV2) } tasks { diff --git a/test-suite/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java b/test-suite/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java index dec6690f8c..8f35476cd4 100644 --- a/test-suite/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java +++ b/test-suite/src/test/java/io/micronaut/docs/function/aws/AnalyticsClientTest.java @@ -2,7 +2,7 @@ import io.micronaut.context.ApplicationContext; import io.micronaut.function.client.FunctionDefinition; -import io.micronaut.function.client.aws.AWSInvokeRequestDefinition; +import io.micronaut.function.client.aws.v2.AwsInvokeRequestDefinition; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; @@ -23,9 +23,9 @@ void testSetupFunctionDefinitions() { assertEquals(1, definitions.size()); assertTrue(definitions.stream().findFirst().isPresent()); - assertTrue(definitions.stream().findFirst().get() instanceof AWSInvokeRequestDefinition); + assertTrue(definitions.stream().findFirst().get() instanceof AwsInvokeRequestDefinition); - AWSInvokeRequestDefinition invokeRequestDefinition = (AWSInvokeRequestDefinition) definitions.stream().findFirst().get(); + AwsInvokeRequestDefinition invokeRequestDefinition = (AwsInvokeRequestDefinition) definitions.stream().findFirst().get(); assertEquals("analytics", invokeRequestDefinition.getName()); //assertEquals("AwsLambdaFunctionName", invokeRequestDefinition.getInvokeRequest().getFunctionName()); diff --git a/test-suite/src/test/resources/application.properties b/test-suite/src/test/resources/application.properties new file mode 100644 index 0000000000..73c92aa9d4 --- /dev/null +++ b/test-suite/src/test/resources/application.properties @@ -0,0 +1 @@ +aws.lambda.functions.analytics.function-name=AwsLambdaFunctionName \ No newline at end of file