diff --git a/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtil.java b/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtil.java index 7c4fa2b970..881378d118 100644 --- a/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtil.java +++ b/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtil.java @@ -10,14 +10,26 @@ import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.api.agent.CloudAccountInfo; import com.newrelic.api.agent.CloudParameters; +import com.newrelic.api.agent.NewRelic; import java.util.function.Function; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class LambdaUtil { private static final String PLATFORM = "aws_lambda"; private static final String NULL_ARN = ""; + private static final FunctionProcessedData NULL_DATA = new FunctionProcessedData(NULL_ARN, NULL_ARN); private static final String PREFIX = "arn:aws:lambda:"; + private static final Pattern FUNC_REF_PATTERN = Pattern.compile( + "(arn:(aws[a-zA-Z-]*)?:lambda:)?" + // arn prefix + "((?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" + // region + "((?\\d{12}):)?" + // account id + "(function:)?" + // constant + "(?[a-zA-Z0-9-\\.]+)" + // function name (only required part) + "(:(?\\$LATEST|[a-zA-Z0-9-]+))?"); // qualifier: version or alias private static final Function CACHE = AgentBridge.collectionFactory.createAccessTimeBasedCache(3600, 8, LambdaUtil::processData); @@ -53,66 +65,47 @@ public static CloudParameters getCloudParameters(FunctionRawData functionRawData */ // Visible for testing static FunctionProcessedData processData(FunctionRawData data) { - String functionRef = data.getFunctionRef(); - - String[] parts = functionRef.split(":"); - - String functionName = NULL_ARN; String arn = NULL_ARN; - if (parts.length == 1) { - // function name: {function-name} - String accountId = getAccountId(data.getSdkClient()); - if (accountId != null) { - String qualifier = data.getQualifier(); - if (qualifier == null) { - arn = PREFIX + data.getRegion() + ":" + accountId + ":function:" + functionRef; - } else { - arn = PREFIX + data.getRegion() + ":" + accountId + ":function:" + functionRef + ":" + qualifier; - } - } - functionName = functionRef; + String functionRef = data.getFunctionRef(); + Matcher matcher = FUNC_REF_PATTERN.matcher(functionRef); + if (!matcher.matches()) { + return NULL_DATA; + } + String region = matcher.group("region"); + String accountId = matcher.group("accountId"); + String qualifier = matcher.group("qualifier"); + String functionName = matcher.group("functionName"); + + if (functionName == null) { + // will not be able to add any data + NewRelic.getAgent().getLogger().log(Level.INFO, "aws-lambda: Unable to assemble ARN: " + functionRef); + return NULL_DATA; + } - } else if (parts.length == 2) { - // function name + qualifier: {function-name}:{qualifier} - String accountId = getAccountId(data.getSdkClient()); - if (accountId != null) { - arn = PREFIX + data.getRegion() + ":" + accountId + ":function:" + functionRef; - } - functionName = parts[0]; + if (region == null) { + // if region is not provided, we will try to get it from the SDK client + region = data.getRegion(); + } + + if (accountId == null) { + // if account id is not provided, we will try to get it from the config + accountId = getAccountId(data.getSdkClient()); + } - } else if (parts.length == 3) { - // partial ARN: {account-id}:function:{function-name} - functionName = parts[2]; - String qualifier = data.getQualifier(); + if (region != null && accountId != null) { if (qualifier == null) { - arn = PREFIX + data.getRegion() + ":" + functionRef; - } else { - arn = PREFIX + data.getRegion() + ":" + functionRef + ":" + qualifier; + qualifier = data.getQualifier(); } - } else if (parts.length == 4) { - // partial ARN with qualifier: {account-id}:function:{function-name}:{qualifier} - functionName = parts[2]; - arn = PREFIX + data.getRegion() + ":" + functionRef; - - } else if (parts.length == 7) { - // full ARN: arn:aws:lambda:{region}:{account-id}:function:{function-name} - functionName = parts[6]; - String qualifier = data.getQualifier(); - if (qualifier == null) { - arn = functionRef; + if (qualifier == null || qualifier.isEmpty() || "$LATEST".equals(qualifier)) { + arn = PREFIX + region + ":" + accountId + ":function:" + functionName; } else { - arn = functionRef + ":" + qualifier; + arn = PREFIX + region + ":" + accountId + ":function:" + functionName + ":" + qualifier; } - - } else if (parts.length == 8) { - // full ARN with qualifier: arn:aws:lambda:{region}:{account-id}:function:{function-name}:{qualifier} - functionName = parts[6]; - arn = functionRef; + } else { + NewRelic.getAgent().getLogger().log(Level.INFO, "aws-lambda: Missing information to assemble ARN."); } - // reference should be invalid if the number of parts do not match any of the expected cases - return new FunctionProcessedData(functionName, arn); } diff --git a/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaAsyncClient_Instrumentation.java b/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaAsyncClient_Instrumentation.java index 56745a414b..c98d261888 100644 --- a/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaAsyncClient_Instrumentation.java +++ b/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaAsyncClient_Instrumentation.java @@ -27,7 +27,7 @@ public abstract class AWSLambdaAsyncClient_Instrumentation { protected abstract String getSigningRegion(); public Future invokeAsync(final InvokeRequest request, AsyncHandler asyncHandler) { - FunctionRawData functionRawData = new FunctionRawData(request.getFunctionName(), request.getQualifier(), getSigningRegion(), this); + FunctionRawData functionRawData = new FunctionRawData(request.getFunctionName(), request.getQualifier(), this.getSigningRegion(), this); CloudParameters cloudParameters = LambdaUtil.getCloudParameters(functionRawData); String functionName = LambdaUtil.getSimpleFunctionName(functionRawData); Segment segment = NewRelic.getAgent().getTransaction().startSegment("Lambda", "invoke/" + functionName); diff --git a/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaClient_Instrumentation.java b/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaClient_Instrumentation.java index fd59911d62..688113f1d0 100644 --- a/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaClient_Instrumentation.java +++ b/instrumentation/aws-java-sdk-lambda-1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaClient_Instrumentation.java @@ -26,7 +26,7 @@ public abstract class AWSLambdaClient_Instrumentation { @Trace(leaf = true) public InvokeResult invoke(InvokeRequest invokeRequest) { - FunctionRawData functionRawData = new FunctionRawData(invokeRequest.getFunctionName(), invokeRequest.getQualifier(), getSigningRegion(), this); + FunctionRawData functionRawData = new FunctionRawData(invokeRequest.getFunctionName(), invokeRequest.getQualifier(), this.getSigningRegion(), this); CloudParameters cloudParameters = LambdaUtil.getCloudParameters(functionRawData); TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod(); tracedMethod.reportAsExternal(cloudParameters); diff --git a/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest.java b/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest.java index e2ca9e0b0f..5d7dc248f6 100644 --- a/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest.java +++ b/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest.java @@ -64,163 +64,6 @@ public void testGetCloudParamArnQualifier() { assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", cloudParameters.getResourceId()); } - @Test - public void testGetArnFunctionName() { - FunctionProcessedData data = LambdaUtil.processData(data("my-function", null)); - assertEquals("", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameClient() { - mockCloudApiClient(); - FunctionProcessedData data = LambdaUtil.processData(data("my-function", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithAlias() { - FunctionProcessedData data = LambdaUtil.processData(data("my-function:alias", null)); - assertEquals("", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithAliasClient() { - mockCloudApiClient(); - FunctionProcessedData data = LambdaUtil.processData(data("my-function:alias", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithVersion() { - FunctionProcessedData data = LambdaUtil.processData(data("my-function:123", null)); - assertEquals("", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithVersionClient() { - mockCloudApiClient(); - FunctionProcessedData data = LambdaUtil.processData(data("my-function:123", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndAliasQualifier() { - FunctionProcessedData data = LambdaUtil.processData(data("my-function", "alias")); - assertEquals("", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndAliasQualifierClient() { - mockCloudApiClient(); - FunctionProcessedData data = LambdaUtil.processData(data("my-function", "alias")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndVersionQualifier() { - FunctionProcessedData data = LambdaUtil.processData(data("my-function", "123")); - assertEquals("", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndVersionQualifierClient() { - mockCloudApiClient(); - FunctionProcessedData data = LambdaUtil.processData(data("my-function", "123")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnPartialArn() { - FunctionProcessedData data = LambdaUtil.processData(data("123456789012:function:my-function", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnPartialArnWithAlias() { - FunctionProcessedData data = LambdaUtil.processData(data("123456789012:function:my-function:alias", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnPartialArnWithVersion() { - FunctionProcessedData data = LambdaUtil.processData(data("123456789012:function:my-function:123", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnPartialArnAndAliasQualifier() { - FunctionProcessedData data = LambdaUtil.processData(data("123456789012:function:my-function", "alias")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnPartialArnAndVersionQualifier() { - FunctionProcessedData data = LambdaUtil.processData(data("123456789012:function:my-function", "123")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFullArn() { - FunctionProcessedData data = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFullArnWithAlias() { - FunctionProcessedData data = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFullArnWithVersion() { - FunctionProcessedData data = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFullArnAndAliasQualifier() { - FunctionProcessedData data = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function", "alias")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnFullArnAndVersionQualifier() { - FunctionProcessedData data = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function", "123")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - @Test - public void testGetArnDifferentRegion() { - FunctionProcessedData data = LambdaUtil.processData(data("arn:aws:lambda:us-west-2:123456789012:function:my-function", null)); - assertEquals("arn:aws:lambda:us-west-2:123456789012:function:my-function", data.getArn()); - assertEquals("my-function", data.getFunctionName()); - } - - private static void mockCloudApiClient() { - when(AgentBridge.cloud.getAccountInfo(any(), eq(CloudAccountInfo.AWS_ACCOUNT_ID))) - .thenReturn("123456789012"); - } - private FunctionRawData data(String functionRef, String qualifier) { return new FunctionRawData(functionRef, qualifier, Regions.US_EAST_1.getName(), this); } diff --git a/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest_ProcessData.java b/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest_ProcessData.java new file mode 100644 index 0000000000..3a22ef159f --- /dev/null +++ b/instrumentation/aws-java-sdk-lambda-1.11.280/src/test/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtilTest_ProcessData.java @@ -0,0 +1,128 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.agent.instrumentation.awsjavasdk1.services.lambda; + +import com.amazonaws.regions.Regions; +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.CloudApi; +import com.newrelic.agent.bridge.NoOpCloud; +import com.newrelic.api.agent.CloudAccountInfo; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(Parameterized.class) +public class LambdaUtilTest_ProcessData { + + @Parameterized.Parameter + public String functionRef; + + @Parameterized.Parameter(1) + public String qualifier; + + @Parameterized.Parameter(2) + public boolean shouldMockCloudApi; + + @Parameterized.Parameter(3) + public String expectedArn; + + @Parameterized.Parameter(4) + public String expectedFunctionName; + + @Parameterized.Parameters(name="{0}, {1}, {2}, {3}, {4}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + // only function name + {"my-function", null, false, "", "my-function"}, + {"my-function:alias", null, false, "", "my-function"}, + {"my-function:alias", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"my-function:123", null, false, "", "my-function"}, + {"my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"my-function", "alias", false, "", "my-function"}, + {"my-function", "alias", true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"my-function", "123", false, "", "my-function"}, + {"my-function", "123", true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + + // account and function name partial ARN + {"123456789012:function:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"123456789012:function:my-function:alias", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"123456789012:function:my-function", "alias", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"123456789012:function:my-function", "123", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + + // full arn + {"arn:aws:lambda:us-east-1:123456789012:function:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function", "alias", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function", "123", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:us-west-2:123456789012:function:my-function", null, false, "arn:aws:lambda:us-west-2:123456789012:function:my-function", "my-function"}, + + // other partial arns + {"arn::lambda:us-east-1:123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn::lambda:us-east-1:123456789012:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn::lambda:us-east-1:function:my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn::lambda:us-east-1:my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:123456789012:function:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"arn:aws:lambda:123456789012:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"us-east-1:123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"us-east-1:function:my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"us-east-1:function:my-function:123", null, false, "", "my-function"}, + {"us-east-1:function:my-function", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"us-east-1:my-function", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"us-east-1:my-function", null, false, "", "my-function"}, + {"123456789012:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + + // ignore $LATEST + {"my-function:$LATEST", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"my-function", "$LATEST", true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + }); + } + + @Before + public void before() { + AgentBridge.cloud = mock(CloudApi.class); + } + + @After + public void after() { + AgentBridge.cloud = NoOpCloud.INSTANCE; + } + + @Test + public void test() { + if (shouldMockCloudApi) { + mockCloudApiClient(); + } + FunctionProcessedData functionProcessedData = LambdaUtil.processData(data(functionRef, qualifier)); + assertEquals(expectedArn, functionProcessedData.getArn()); + assertEquals(expectedFunctionName, functionProcessedData.getFunctionName()); + } + + private static void mockCloudApiClient() { + when(AgentBridge.cloud.getAccountInfo(any(), eq(CloudAccountInfo.AWS_ACCOUNT_ID))) + .thenReturn("123456789012"); + } + + private FunctionRawData data(String functionRef, String qualifier) { + return new FunctionRawData(functionRef, qualifier, Regions.US_EAST_1.getName(), this); + } + +} \ No newline at end of file diff --git a/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/FunctionRawData.java b/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/FunctionRawData.java index 68c06d0a50..52cb130167 100644 --- a/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/FunctionRawData.java +++ b/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/FunctionRawData.java @@ -9,6 +9,7 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.regions.Region; import java.lang.ref.WeakReference; import java.util.Objects; @@ -22,7 +23,6 @@ public class FunctionRawData { // the code only cares about the region, but the config is stored // to prevent unnecessary calls to get the region private final WeakReference config; - private String awsRegion; private final WeakReference sdkClient; public FunctionRawData(String functionRef, String qualifier, SdkClientConfiguration config, Object sdkClient) { @@ -41,16 +41,14 @@ public String getQualifier() { } public String getRegion() { - if (awsRegion == null) { - SdkClientConfiguration config = this.config.get(); - if (config != null) { - Object region = config.option(AwsClientOption.AWS_REGION); - if (region != null) { - awsRegion = region.toString(); - } + SdkClientConfiguration config = this.config.get(); + if (config != null) { + Region region = config.option(AwsClientOption.AWS_REGION); + if (region != null) { + return region.id(); } } - return awsRegion; + return null; } public Object getSdkClient() { diff --git a/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtil.java b/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtil.java index 751dd99e09..85fc73cee3 100644 --- a/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtil.java +++ b/instrumentation/aws-java-sdk-lambda-2.1/src/main/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtil.java @@ -10,14 +10,27 @@ import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.api.agent.CloudAccountInfo; import com.newrelic.api.agent.CloudParameters; +import com.newrelic.api.agent.NewRelic; import java.util.function.Function; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class LambdaUtil { private static final String PLATFORM = "aws_lambda"; private static final String NULL_ARN = ""; + private static final FunctionProcessedData NULL_DATA = new FunctionProcessedData(NULL_ARN, NULL_ARN); private static final String PREFIX = "arn:aws:lambda:"; + private static final Pattern FUNC_REF_PATTERN = Pattern.compile( + "(arn:(aws[a-zA-Z-]*)?:lambda:)?" + // arn prefix + "((?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" + // region + "((?\\d{12}):)?" + // account id + "(function:)?" + // constant + "(?[a-zA-Z0-9-\\.]+)" + // function name (only required part) + "(:(?\\$LATEST|[a-zA-Z0-9-]+))?"); // qualifier: version or alias + private static final Function CACHE = AgentBridge.collectionFactory.createAccessTimeBasedCache(3600, 8, LambdaUtil::processData); @@ -53,66 +66,48 @@ public static CloudParameters getCloudParameters(FunctionRawData functionRawData */ // Visible for testing static FunctionProcessedData processData(FunctionRawData data) { + String arn = NULL_ARN; + String functionRef = data.getFunctionRef(); + Matcher matcher = FUNC_REF_PATTERN.matcher(functionRef); + if (!matcher.matches()) { + return NULL_DATA; + } - String[] parts = functionRef.split(":"); + String region = matcher.group("region"); + String accountId = matcher.group("accountId"); + String qualifier = matcher.group("qualifier"); + String functionName = matcher.group("functionName"); - String functionName = NULL_ARN; - String arn = NULL_ARN; + if (functionName == null) { + // will not be able to add any data + NewRelic.getAgent().getLogger().log(Level.INFO, "aws-lambda: Unable to assemble ARN: " + functionRef); + return NULL_DATA; + } - if (parts.length == 1) { - // function name: {function-name} - String accountId = getAccountId(data.getSdkClient()); - if (accountId != null) { - String qualifier = data.getQualifier(); - if (qualifier == null) { - arn = PREFIX + data.getRegion() + ":" + accountId + ":function:" + functionRef; - } else { - arn = PREFIX + data.getRegion() + ":" + accountId + ":function:" + functionRef + ":" + qualifier; - } - } - functionName = functionRef; + if (region == null) { + // if region is not provided, we will try to get it from the SDK client + region = data.getRegion(); + } - } else if (parts.length == 2) { - // function name + qualifier: {function-name}:{qualifier} - String accountId = getAccountId(data.getSdkClient()); - if (accountId != null) { - arn = PREFIX + data.getRegion() + ":" + accountId + ":function:" + functionRef; - } - functionName = parts[0]; + if (accountId == null) { + // if account id is not provided, we will try to get it from the config + accountId = getAccountId(data.getSdkClient()); + } - } else if (parts.length == 3) { - // partial ARN: {account-id}:function:{function-name} - functionName = parts[2]; - String qualifier = data.getQualifier(); + if (region != null && accountId != null) { if (qualifier == null) { - arn = PREFIX + data.getRegion() + ":" + functionRef; - } else { - arn = PREFIX + data.getRegion() + ":" + functionRef + ":" + qualifier; + qualifier = data.getQualifier(); } - } else if (parts.length == 4) { - // partial ARN with qualifier: {account-id}:function:{function-name}:{qualifier} - functionName = parts[2]; - arn = PREFIX + data.getRegion() + ":" + functionRef; - - } else if (parts.length == 7) { - // full ARN: arn:aws:lambda:{region}:{account-id}:function:{function-name} - functionName = parts[6]; - String qualifier = data.getQualifier(); - if (qualifier == null) { - arn = functionRef; + if (qualifier == null || qualifier.isEmpty() || "$LATEST".equals(qualifier)) { + arn = PREFIX + region + ":" + accountId + ":function:" + functionName; } else { - arn = functionRef + ":" + qualifier; + arn = PREFIX + region + ":" + accountId + ":function:" + functionName + ":" + qualifier; } - - } else if (parts.length == 8) { - // full ARN with qualifier: arn:aws:lambda:{region}:{account-id}:function:{function-name}:{qualifier} - functionName = parts[6]; - arn = functionRef; + } else { + NewRelic.getAgent().getLogger().log(Level.INFO, "aws-lambda: Missing information to assemble ARN."); } - // reference should be invalid if the number of parts do not match any of the expected cases - return new FunctionProcessedData(functionName, arn); } diff --git a/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest.java b/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest.java index 080bc96c13..6df030b21a 100644 --- a/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest.java +++ b/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest.java @@ -29,16 +29,6 @@ public class LambdaUtilTest { - @Before - public void before() { - AgentBridge.cloud = mock(CloudApi.class); - } - - @After - public void after() { - AgentBridge.cloud = NoOpCloud.INSTANCE; - } - @Test public void testGetCloudParamFunctionName() { FunctionRawData functionRawData = data("my-function", null); @@ -66,160 +56,11 @@ public void testGetCloudParamArnQualifier() { assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", cloudParameters.getResourceId()); } - @Test - public void testGetArnFunctionName() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function", null)); - assertEquals("", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithAlias() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function:alias", null)); - assertEquals("", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithAliasClient() { - mockCloudApiClient(); - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function:alias", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithVersion() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function:123", null)); - assertEquals("", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameWithVersionClient() { - mockCloudApiClient(); - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function:123", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndAliasQualifier() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function", "alias")); - assertEquals("", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndAliasQualifierClient() { - mockCloudApiClient(); - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function", "alias")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndVersionQualifier() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function", "123")); - assertEquals("", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFunctionNameAndVersionQualifierClient() { - mockCloudApiClient(); - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("my-function", "123")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnPartialArn() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("123456789012:function:my-function", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnPartialArnWithAlias() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("123456789012:function:my-function:alias", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnPartialArnWithVersion() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("123456789012:function:my-function:123", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnPartialArnAndAliasQualifier() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("123456789012:function:my-function", "alias")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnPartialArnAndVersionQualifier() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("123456789012:function:my-function", "123")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFullArn() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFullArnWithAlias() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData( - data("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFullArnWithVersion() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", null)); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFullArnAndAliasQualifier() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function", "alias")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnFullArnAndVersionQualifier() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("arn:aws:lambda:us-east-1:123456789012:function:my-function", "123")); - assertEquals("arn:aws:lambda:us-east-1:123456789012:function:my-function:123", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - @Test - public void testGetArnDifferentRegion() { - FunctionProcessedData functionProcessedData = LambdaUtil.processData(data("arn:aws:lambda:us-west-2:123456789012:function:my-function", null)); - assertEquals("arn:aws:lambda:us-west-2:123456789012:function:my-function", functionProcessedData.getArn()); - assertEquals("my-function", functionProcessedData.getFunctionName()); - } - - private FunctionRawData data(String functionRef, String number) { + private FunctionRawData data(String functionRef, String number) { SdkClientConfiguration config = SdkClientConfiguration.builder() .option(AwsClientOption.AWS_REGION, Region.US_EAST_1) .build(); return new FunctionRawData(functionRef, number, config, new Object()); } - private static void mockCloudApiClient() { - when(AgentBridge.cloud.getAccountInfo(any(), eq(CloudAccountInfo.AWS_ACCOUNT_ID))) - .thenReturn("123456789012"); - } } \ No newline at end of file diff --git a/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest_ProcessData.java b/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest_ProcessData.java new file mode 100644 index 0000000000..10058c35f6 --- /dev/null +++ b/instrumentation/aws-java-sdk-lambda-2.1/src/test/java/com/agent/instrumentation/awsjavasdk2/services/lambda/LambdaUtilTest_ProcessData.java @@ -0,0 +1,136 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.agent.instrumentation.awsjavasdk2.services.lambda; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.CloudApi; +import com.newrelic.agent.bridge.NoOpCloud; +import com.newrelic.api.agent.CloudAccountInfo; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.regions.Region; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +@RunWith(Parameterized.class) +public class LambdaUtilTest_ProcessData { + + @Parameterized.Parameter + public String functionRef; + + @Parameterized.Parameter(1) + public String qualifier; + + @Parameterized.Parameter(2) + public boolean shouldMockCloudApi; + + @Parameterized.Parameter(3) + public String expectedArn; + + @Parameterized.Parameter(4) + public String expectedFunctionName; + + @Parameterized.Parameters(name="{0}, {1}, {2}, {3}, {4}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + // only function name + {"my-function", null, false, "", "my-function"}, + {"my-function:alias", null, false, "", "my-function"}, + {"my-function:alias", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"my-function:123", null, false, "", "my-function"}, + {"my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"my-function", "alias", false, "", "my-function"}, + {"my-function", "alias", true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"my-function", "123", false, "", "my-function"}, + {"my-function", "123", true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + + // account and function name partial ARN + {"123456789012:function:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"123456789012:function:my-function:alias", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"123456789012:function:my-function", "alias", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"123456789012:function:my-function", "123", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + + // full arn + {"arn:aws:lambda:us-east-1:123456789012:function:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function", "alias", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:alias", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:function:my-function", "123", false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:us-west-2:123456789012:function:my-function", null, false, "arn:aws:lambda:us-west-2:123456789012:function:my-function", "my-function"}, + + // other partial arns + {"arn::lambda:us-east-1:123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn::lambda:us-east-1:123456789012:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn::lambda:us-east-1:function:my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn::lambda:us-east-1:my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:us-east-1:123456789012:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"arn:aws:lambda:123456789012:function:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"arn:aws:lambda:123456789012:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"us-east-1:123456789012:function:my-function:123", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"us-east-1:function:my-function:123", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function:123", "my-function"}, + {"us-east-1:function:my-function:123", null, false, "", "my-function"}, + {"us-east-1:function:my-function", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"us-east-1:my-function", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"us-east-1:my-function", null, false, "", "my-function"}, + {"123456789012:my-function", null, false, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + + // ignore $LATEST + {"my-function:$LATEST", null, true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + {"my-function", "$LATEST", true, "arn:aws:lambda:us-east-1:123456789012:function:my-function", "my-function"}, + }); + } + + @Before + public void before() { + AgentBridge.cloud = mock(CloudApi.class); + } + + @After + public void after() { + AgentBridge.cloud = NoOpCloud.INSTANCE; + } + + @Test + public void test() { + if (shouldMockCloudApi) { + mockCloudApiClient(); + } + FunctionProcessedData functionProcessedData = LambdaUtil.processData(data(functionRef, qualifier)); + assertEquals(expectedArn, functionProcessedData.getArn()); + assertEquals(expectedFunctionName, functionProcessedData.getFunctionName()); + } + + private FunctionRawData data(String functionRef, String number) { + SdkClientConfiguration config = SdkClientConfiguration.builder() + .option(AwsClientOption.AWS_REGION, Region.US_EAST_1) + .build(); + return new FunctionRawData(functionRef, number, config, new Object()); + } + + private static void mockCloudApiClient() { + when(AgentBridge.cloud.getAccountInfo(any(), eq(CloudAccountInfo.AWS_ACCOUNT_ID))) + .thenReturn("123456789012"); + } + +} \ No newline at end of file