From ef8e2855b3f1b223032ca32462404239b705b901 Mon Sep 17 00:00:00 2001 From: Sai Chandupatla Date: Fri, 8 Jul 2022 19:42:07 -0700 Subject: [PATCH 1/4] Implemented Response Handling and Parsing Methods for output --- core/imds/pom.xml | 6 + .../amazon/awssdk/imds/Ec2Metadata.java | 5 +- .../imds/internal/DefaultEc2Metadata.java | 8 +- .../imds/internal/MetadataResponse.java | 123 +++++++++++++ .../amazon/awssdk/imds/Ec2MetadataTest.java | 41 +++-- .../awssdk/imds/MetadataResponseTest.java | 166 ++++++++++++++++++ 6 files changed, 325 insertions(+), 24 deletions(-) create mode 100644 core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java create mode 100644 core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java diff --git a/core/imds/pom.xml b/core/imds/pom.xml index bc254fe71b00..defdd318b01c 100644 --- a/core/imds/pom.xml +++ b/core/imds/pom.xml @@ -93,6 +93,12 @@ ${awsjavasdk.version} compile + + software.amazon.awssdk + json-utils + ${awsjavasdk.version} + compile + diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java b/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java index d40150d50f30..3d799f4d0f06 100644 --- a/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java @@ -22,6 +22,7 @@ import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.imds.internal.DefaultEc2Metadata; import software.amazon.awssdk.imds.internal.EndpointMode; +import software.amazon.awssdk.imds.internal.MetadataResponse; /** @@ -33,9 +34,9 @@ public interface Ec2Metadata { /** * Gets the specified instance metadata value by the given path. * @param path Input path - * @return Instance metadata value + * @return Instance metadata value as part of MetadataResponse Object */ - String get(String path); + MetadataResponse get(String path); /** * @return The Builder Object consisting all the fields. diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java b/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java index 5d57f62ef892..a2cb1416469c 100644 --- a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java @@ -144,11 +144,12 @@ public String toString() { /** * Gets the specified instance metadata value by the given path. * @param path Input path - * @return Instance metadata value + * @return Instance metadata value as part of MetadataResponse Object */ @Override - public String get(String path) { + public MetadataResponse get(String path) { + MetadataResponse metadataResponse = null; String data = null; AbortableInputStream abortableInputStream = null; try { @@ -163,6 +164,7 @@ public String get(String path) { if (statusCode == HttpURLConnection.HTTP_OK && responseBody.isPresent()) { abortableInputStream = responseBody.get(); data = IoUtils.toUtf8String(abortableInputStream); + metadataResponse = new MetadataResponse(data); } else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND) { throw SdkServiceException.builder() .message("The requested metadata at path ( " + path + " ) is not found ").build(); @@ -183,7 +185,7 @@ public String get(String path) { IoUtils.closeQuietly(abortableInputStream, log); } - return data; + return metadataResponse; } private String getToken() throws IOException { diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java b/core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java new file mode 100644 index 000000000000..6ad91ed4a65e --- /dev/null +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java @@ -0,0 +1,123 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.imds.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.protocols.jsoncore.JsonNode; +import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; + +/** + * The class is used for Response Handling and Parsing. + */ +@SdkPublicApi +public class MetadataResponse { + + private static final Logger log = LoggerFactory.getLogger(MetadataResponse.class); + + private static final JsonNodeParser JSON_NODE_PARSER = JsonNode.parser(); + + String body; + + public MetadataResponse(String body) { + this.body = body; + } + + public String asString() { + return body; + } + + /** + * Method to parse the json String into a list of Strings + * @return List obtained by splitting the json on "\n" + */ + public List asList() { + + if (null != body && body.contains("\n")) { + + return Arrays.asList(body.split("\n")); + } + + return Collections.emptyList(); + } + + /** + * Method to get the String Array Values of the key in jsonResponse + * @param key The key / field to retrieve from jsonResponse + * @return String Array of the values of the key in the jsonResponse + */ + public String[] getStringArrayValuesFromJson(String key) { + + if (null != key) { + + Map jsonNode = JSON_NODE_PARSER.parse(body).asObject(); + return stringArrayValue(jsonNode.get(key)); + } + + return new String[0]; + + } + + /** + * Method to get the String Array Values of the key in jsonResponse + * @param key The key / field to retrieve from jsonResponse + * @return String value of the key in the jsonResponse + */ + public String getStringValueFromJson(String key) { + + if (null != key) { + + Map jsonNode = JSON_NODE_PARSER.parse(body).asObject(); + return stringValue(jsonNode.get(key)); + + } + + return null; + } + + private String[] stringArrayValue(JsonNode jsonNode) { + + if (null != jsonNode && jsonNode.isArray()) { + try { + return jsonNode.asArray() + .stream() + .filter(JsonNode::isString) + .map(JsonNode::asString) + .toArray(String[]::new); + } catch (Exception e) { + log.warn("Unable to parse jsonNode instance info : " + e.getMessage(), e); + } + } + return new String[0]; + } + + private String stringValue(JsonNode jsonNode) { + + if (null != jsonNode && jsonNode.isString()) { + try { + return jsonNode.asString(); + } catch (Exception e) { + log.warn("Unable to parse jsonNode instance info : " + e.getMessage(), e); + } + } + return null; + } +} diff --git a/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java b/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java index cc3a1f2d53dd..fed59a5ba4ed 100644 --- a/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java +++ b/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java @@ -41,6 +41,9 @@ import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkServiceException; import software.amazon.awssdk.http.ExecutableHttpRequest; +import software.amazon.awssdk.imds.internal.MetadataResponse; +import software.amazon.awssdk.protocols.jsoncore.JsonNode; +import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; /** * Unit Tests to test the Ec2Metadata Client functionality @@ -65,6 +68,8 @@ public class Ec2MetadataTest { @Rule public WireMockRule mockMetadataEndpoint = new WireMockRule(); + private static final JsonNodeParser jsonParser = JsonNode.parser(); + @Before public void methodSetup() { System.setProperty(SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.property(), "http://localhost:" + mockMetadataEndpoint.port()); @@ -74,8 +79,9 @@ public void methodSetup() { @Test public void when_dummy_string_is_returned(){ - when(ec2Metadata.get("/ami-id")).thenReturn("IMDS"); - assertThat(ec2Metadata.get("/ami-id")).isEqualTo("IMDS"); + MetadataResponse metadataResponse = new MetadataResponse("IMDS"); + when(ec2Metadata.get("/ami-id")).thenReturn(metadataResponse); + assertThat(ec2Metadata.get("/ami-id").asString()).isEqualTo("IMDS"); } @@ -93,9 +99,9 @@ public void get_AmiId_onMetadataResource_200_Success() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}"))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); - assertThat(data).isEqualTo("{}"); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); + assertThat(metadataResponse.asString()).isEqualTo("{}"); WireMock.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(EC2_METADATA_TOKEN_TTL_HEADER, equalTo("21600"))); WireMock.verify(getRequestedFor(urlPathEqualTo(AMI_ID_RESOURCE)).withHeader(TOKEN_HEADER, equalTo("some-token"))); @@ -111,7 +117,7 @@ public void get_AmiId_onMetadataResource_404Error_throws() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}").withStatus(404))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); } @Test @@ -121,9 +127,8 @@ public void get_AmiId_onMetadataResource_401Error_throws() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}").withStatus(401))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); - - assertThat(data).isNull(); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); + assertThat(metadataResponse).isNull(); } @Test @@ -133,9 +138,9 @@ public void get_AmiId_onMetadataResource_IOException_throws() { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withFixedDelay(Integer.MAX_VALUE))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); - assertThat(data).isNull(); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); + assertThat(metadataResponse).isNull(); WireMock.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(EC2_METADATA_TOKEN_TTL_HEADER, equalTo("21600"))); WireMock.verify(getRequestedFor(urlPathEqualTo(AMI_ID_RESOURCE)).withHeader(TOKEN_HEADER, equalTo("some-token"))); @@ -151,7 +156,7 @@ public void get_AmiId_onTokenResource_403Error_throws() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}"))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); } @Test @@ -161,9 +166,9 @@ public void get_AmiId_onTokenResource_401Error_throws() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}"))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); - assertThat(data).isNull(); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); + assertThat(metadataResponse).isNull(); WireMock.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(EC2_METADATA_TOKEN_TTL_HEADER, equalTo("21600"))); } @@ -174,12 +179,10 @@ public void getAmiId_onTokenResource_IOError_throws() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}"))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); - - assertThat(data).isNull(); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); + assertThat(metadataResponse).isNull(); WireMock.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(EC2_METADATA_TOKEN_TTL_HEADER, equalTo("21600"))); - // WireMock.verify(getRequestedFor(urlPathEqualTo(AMI_ID_RESOURCE)).withHeader(TOKEN_HEADER, equalTo("some-token"))); } @Test public void getAmiId_onTokenResource_200() throws IOException { @@ -187,9 +190,9 @@ public void getAmiId_onTokenResource_200() throws IOException { stubFor(get(urlPathEqualTo(AMI_ID_RESOURCE)).willReturn(aResponse().withBody("{}"))); Ec2Metadata ec2Metadata = Ec2Metadata.builder().endpoint(URI.create("http://localhost:8080")).build(); - String data = ec2Metadata.get("/latest/meta-data/ami-id"); - assertThat(data).isEqualTo("{}"); + MetadataResponse metadataResponse = ec2Metadata.get("/latest/meta-data/ami-id"); + assertThat(metadataResponse.asString()).isEqualTo("{}"); WireMock.verify(putRequestedFor(urlPathEqualTo(TOKEN_RESOURCE_PATH)).withHeader(EC2_METADATA_TOKEN_TTL_HEADER, equalTo("21600"))); WireMock.verify(getRequestedFor(urlPathEqualTo(AMI_ID_RESOURCE)).withHeader(TOKEN_HEADER, equalTo("some-token"))); diff --git a/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java b/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java new file mode 100644 index 000000000000..735b4fb53a08 --- /dev/null +++ b/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java @@ -0,0 +1,166 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.imds; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.List; +import org.junit.Test; +import software.amazon.awssdk.imds.internal.MetadataResponse; + +/** + * The class tests the utility methods provided by MetadataResponse Class . + */ +public class MetadataResponseTest { + + @Test + public void get_devpayProductCodes_from_JsonResponse_success() throws IOException { + + String jsonResponse = "{" + + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," + + "\"instanceType\":\"m1.small\"," + + "\"imageId\":\"ami-a49665cc\"," + + "\"instanceId\":\"i-6b2de041\"," + + "\"billingProducts\":[\"foo\"]," + + "\"architecture\":\"x86_64\"," + + "\"accountId\":\"599169622985\"," + + "\"kernelId\":\"aki-919dcaf8\"," + + "\"ramdiskId\":\"baz\"," + + "\"region\":\"us-east-1\"," + + "\"version\":\"2010-08-31\"," + + "\"availabilityZone\":\"us-east-1b\"," + + "\"privateIp\":\"10.201.215.38\"," + + "\"devpayProductCodes\":[\"bar\",\"foo\"]," + + "\"marketplaceProductCodes\":[\"qaz\"]" + + "}"; + + MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); + String[] result = metadataResponse.getStringArrayValuesFromJson("devpayProductCodes"); + assertThat(result).hasSize(2); + } + + @Test + public void get_imageId_from_JsonResponse_success() throws IOException { + + String jsonResponse = "{" + + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," + + "\"instanceType\":\"m1.small\"," + + "\"imageId\":\"ami-a49665cc\"," + + "\"instanceId\":\"i-6b2de041\"," + + "\"billingProducts\":[\"foo\"]," + + "\"architecture\":\"x86_64\"," + + "\"accountId\":\"599169622985\"," + + "\"kernelId\":\"aki-919dcaf8\"," + + "\"ramdiskId\":\"baz\"," + + "\"region\":\"us-east-1\"," + + "\"version\":\"2010-08-31\"," + + "\"availabilityZone\":\"us-east-1b\"," + + "\"privateIp\":\"10.201.215.38\"," + + "\"devpayProductCodes\":[\"bar\"]," + + "\"marketplaceProductCodes\":[\"qaz\"]" + + "}"; + + MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); + String result = metadataResponse.getStringValueFromJson("imageId"); + assertThat(result).isEqualTo("ami-a49665cc"); + } + + @Test + public void check_asString_success() throws IOException { + + String response = "foobar"; + + MetadataResponse metadataResponse = new MetadataResponse(response); + String result = metadataResponse.asString(); + assertThat(result).isEqualTo(response); + + } + + @Test + public void check_asList_success() throws IOException { + + String response = "sai\ntest"; + + MetadataResponse metadataResponse = new MetadataResponse(response); + List result = metadataResponse.asList(); + assertThat(result).hasSize(2); + + } + + @Test + public void get_devpayProductCodes_from_JsonResponse_failure() throws IOException { + + String jsonResponse = "{" + + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," + + "\"instanceType\":\"m1.small\"," + + "\"imageId\":\"ami-a49665cc\"," + + "\"instanceId\":\"i-6b2de041\"," + + "\"billingProducts\":[\"foo\"]," + + "\"architecture\":\"x86_64\"," + + "\"accountId\":\"599169622985\"," + + "\"kernelId\":\"aki-919dcaf8\"," + + "\"ramdiskId\":\"baz\"," + + "\"region\":\"us-east-1\"," + + "\"version\":\"2010-08-31\"," + + "\"availabilityZone\":\"us-east-1b\"," + + "\"privateIp\":\"10.201.215.38\"," + + "\"devpayProductCodes\":[\"bar\",\"foo\"]," + + "\"marketplaceProductCodes\":[\"qaz\"]" + + "}"; + + MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); + String[] result = metadataResponse.getStringArrayValuesFromJson("devpayProductCodes1"); + assertThat(result).isNullOrEmpty(); + } + + @Test + public void get_imageId_from_JsonResponse_failure() throws IOException { + + String jsonResponse = "{" + + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," + + "\"instanceType\":\"m1.small\"," + + "\"imageId\":\"ami-a49665cc\"," + + "\"instanceId\":\"i-6b2de041\"," + + "\"billingProducts\":[\"foo\"]," + + "\"architecture\":\"x86_64\"," + + "\"accountId\":\"599169622985\"," + + "\"kernelId\":\"aki-919dcaf8\"," + + "\"ramdiskId\":\"baz\"," + + "\"region\":\"us-east-1\"," + + "\"version\":\"2010-08-31\"," + + "\"availabilityZone\":\"us-east-1b\"," + + "\"privateIp\":\"10.201.215.38\"," + + "\"devpayProductCodes\":[\"bar\",\"foo\"]," + + "\"marketplaceProductCodes\":[\"qaz\"]" + + "}"; + + MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); + String result = metadataResponse.getStringValueFromJson("imageId1"); + assertThat(result).isNull(); + } + + @Test + public void get_outputIamCredList_from_list_failure() throws IOException { + + String response = "test1-test2"; + + MetadataResponse metadataResponse = new MetadataResponse(response); + List result = metadataResponse.asList(); + assertThat(result).isNullOrEmpty(); + + } +} From 78e8a3d7e9562b86339e526cfb9b29940da835b8 Mon Sep 17 00:00:00 2001 From: Sai Chandupatla Date: Mon, 11 Jul 2022 15:01:10 -0700 Subject: [PATCH 2/4] Added Javadoc info --- .../amazon/awssdk/imds/Ec2Metadata.java | 1 - .../imds/{internal => }/MetadataResponse.java | 84 ++++++++++++++++--- .../imds/internal/DefaultEc2Metadata.java | 1 + .../amazon/awssdk/imds/Ec2MetadataTest.java | 2 - .../awssdk/imds/MetadataResponseTest.java | 1 - 5 files changed, 75 insertions(+), 14 deletions(-) rename core/imds/src/main/java/software/amazon/awssdk/imds/{internal => }/MetadataResponse.java (52%) diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java b/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java index 3d799f4d0f06..1d89b27c6bb1 100644 --- a/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/Ec2Metadata.java @@ -22,7 +22,6 @@ import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.imds.internal.DefaultEc2Metadata; import software.amazon.awssdk.imds.internal.EndpointMode; -import software.amazon.awssdk.imds.internal.MetadataResponse; /** diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java b/core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java similarity index 52% rename from core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java rename to core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java index 6ad91ed4a65e..a4df4c292549 100644 --- a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/MetadataResponse.java +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package software.amazon.awssdk.imds.internal; +package software.amazon.awssdk.imds; import java.util.Arrays; import java.util.Collections; @@ -26,7 +26,9 @@ import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; /** - * The class is used for Response Handling and Parsing. + * The class is used for Response Handling and Parsing the metadata fetched by the get call in the {@link Ec2Metadata} interface. + * Metadata is stored in the instance variable body. The class provides convenience methods to the users to parse the + * metadata as a String, List and to parse the metadata in the JsonResponse according to the key. */ @SdkPublicApi public class MetadataResponse { @@ -35,19 +37,50 @@ public class MetadataResponse { private static final JsonNodeParser JSON_NODE_PARSER = JsonNode.parser(); - String body; + private final String body; public MetadataResponse(String body) { this.body = body; } + /** + * Returns the Metadata Response body as a String. This method can be used for parsing the retrieved + * singular metadata from IMDS. + * + * @return String Representation of the Metadata Response Body. + * + *

+ * Example: + *

+     * {@code
+     *
+     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * MetadataResponse metadataResponse = client.get("/latest/meta-data/ami-id");
+     * String response = metadataResponse.asString();
+     *  }
+     *  
+ */ public String asString() { return body; + } /** - * Method to parse the json String into a list of Strings - * @return List obtained by splitting the json on "\n" + * Parses the response String into a list of Strings split by delimiter ("\n"). This method can be used for parsing the + * list-type metadata from IMDS. + * + * @return List Representation of the Metadata Response Body. + * + *

+ * Example: + *

+     * {@code
+     *
+     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * MetadataResponse metadataResponse = client.get("/latest/meta-data/ancestor-ami-ids");
+     * Listresponse = metadataResponse.asList();
+     * }
+     * 
*/ public List asList() { @@ -60,10 +93,25 @@ public List asList() { } /** - * Method to get the String Array Values of the key in jsonResponse - * @param key The key / field to retrieve from jsonResponse - * @return String Array of the values of the key in the jsonResponse + * Parses the response String to get the String Array Values of the key in jsonResponse. This method can be used for + * parsing the metadata in a String Json Format. + * + * @param key The key / field to retrieve from jsonResponse. + * @return String Array of the values of the key in the jsonResponse. + * Returns Null in case the key is null or key doesn't exist in the json. + * + *

+ * Example: + *

+     * {@code
+     *
+     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * MetadataResponse metadataResponse = client.get("/latest/dynamic/instance-identity/document");
+     * String[] devpayProductCodes = metadataResponse.getStringArrayValuesFromJson("devpayProductCodes");
+     * }
+     * 
*/ + public String[] getStringArrayValuesFromJson(String key) { if (null != key) { @@ -77,10 +125,26 @@ public String[] getStringArrayValuesFromJson(String key) { } /** - * Method to get the String Array Values of the key in jsonResponse + * Parses the response String to get the String Value of the key in jsonResponse. This method can be used for + * parsing the metadata in a String Json Format. + * * @param key The key / field to retrieve from jsonResponse - * @return String value of the key in the jsonResponse + * @return Returns the String Value of the key in the jsonResponse . + * + * Returns Null in case the key is null or key doesn't exist in the json. + * + *

+ * Example: + *

+     * {@code
+     *
+     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * MetadataResponse metadataResponse = client.get("/latest/dynamic/instance-identity/document");
+     * String region = metadataResponse.getStringArrayValuesFromJson("region");
+     * }
+     * 
*/ + public String getStringValueFromJson(String key) { if (null != key) { diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java b/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java index a2cb1416469c..815031206eb4 100644 --- a/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java @@ -36,6 +36,7 @@ import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.imds.Ec2Metadata; +import software.amazon.awssdk.imds.MetadataResponse; import software.amazon.awssdk.utils.IoUtils; /** diff --git a/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java b/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java index fed59a5ba4ed..70bf073ed144 100644 --- a/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java +++ b/core/imds/src/test/java/software/amazon/awssdk/imds/Ec2MetadataTest.java @@ -40,8 +40,6 @@ import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkServiceException; -import software.amazon.awssdk.http.ExecutableHttpRequest; -import software.amazon.awssdk.imds.internal.MetadataResponse; import software.amazon.awssdk.protocols.jsoncore.JsonNode; import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; diff --git a/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java b/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java index 735b4fb53a08..83a4bd1340d4 100644 --- a/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java +++ b/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.util.List; import org.junit.Test; -import software.amazon.awssdk.imds.internal.MetadataResponse; /** * The class tests the utility methods provided by MetadataResponse Class . From 91287936cb7e6d59a8abd5f714cca7b76a9508ed Mon Sep 17 00:00:00 2001 From: Sai Chandupatla Date: Thu, 14 Jul 2022 13:09:18 -0700 Subject: [PATCH 3/4] Implemented PR comments --- core/imds/pom.xml | 6 + .../amazon/awssdk/imds/MetadataResponse.java | 106 +++---------- .../awssdk/imds/MetadataResponseTest.java | 150 +++++++----------- 3 files changed, 77 insertions(+), 185 deletions(-) diff --git a/core/imds/pom.xml b/core/imds/pom.xml index defdd318b01c..610b7c20d377 100644 --- a/core/imds/pom.xml +++ b/core/imds/pom.xml @@ -99,6 +99,12 @@ ${awsjavasdk.version} compile + + software.amazon.awssdk + aws-json-protocol + ${awsjavasdk.version} + test + diff --git a/core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java b/core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java index a4df4c292549..eefd16cfe8ce 100644 --- a/core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java +++ b/core/imds/src/main/java/software/amazon/awssdk/imds/MetadataResponse.java @@ -15,32 +15,33 @@ package software.amazon.awssdk.imds; +import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.core.document.Document; +import software.amazon.awssdk.protocols.json.internal.unmarshall.document.DocumentUnmarshaller; import software.amazon.awssdk.protocols.jsoncore.JsonNode; import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; +import software.amazon.awssdk.utils.Validate; /** * The class is used for Response Handling and Parsing the metadata fetched by the get call in the {@link Ec2Metadata} interface. - * Metadata is stored in the instance variable body. The class provides convenience methods to the users to parse the - * metadata as a String, List and to parse the metadata in the JsonResponse according to the key. + * The class provides convenience methods to the users to parse the metadata as a String, List and Document. */ @SdkPublicApi public class MetadataResponse { private static final Logger log = LoggerFactory.getLogger(MetadataResponse.class); - private static final JsonNodeParser JSON_NODE_PARSER = JsonNode.parser(); + private static final JsonNodeParser JSON_NODE_PARSER = JsonNode.parserBuilder().removeErrorLocations(true).build(); private final String body; public MetadataResponse(String body) { - this.body = body; + this.body = Validate.notNull(body, "Metadata is null"); } /** @@ -54,7 +55,7 @@ public MetadataResponse(String body) { *
      * {@code
      *
-     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * Ec2Metadata ec2Metadata = Ec2Metadata.create();
      * MetadataResponse metadataResponse = client.get("/latest/meta-data/ami-id");
      * String response = metadataResponse.asString();
      *  }
@@ -76,112 +77,41 @@ public String asString() {
      * 
      * {@code
      *
-     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * Ec2Metadata ec2Metadata = Ec2Metadata.create();
      * MetadataResponse metadataResponse = client.get("/latest/meta-data/ancestor-ami-ids");
      * Listresponse = metadataResponse.asList();
      * }
      * 
*/ public List asList() { - - if (null != body && body.contains("\n")) { - - return Arrays.asList(body.split("\n")); - } - - return Collections.emptyList(); + return Arrays.asList(body.split("\n")); } - /** - * Parses the response String to get the String Array Values of the key in jsonResponse. This method can be used for - * parsing the metadata in a String Json Format. - * - * @param key The key / field to retrieve from jsonResponse. - * @return String Array of the values of the key in the jsonResponse. - * Returns Null in case the key is null or key doesn't exist in the json. - * - *

- * Example: - *

-     * {@code
-     *
-     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
-     * MetadataResponse metadataResponse = client.get("/latest/dynamic/instance-identity/document");
-     * String[] devpayProductCodes = metadataResponse.getStringArrayValuesFromJson("devpayProductCodes");
-     * }
-     * 
- */ - - public String[] getStringArrayValuesFromJson(String key) { - - if (null != key) { - - Map jsonNode = JSON_NODE_PARSER.parse(body).asObject(); - return stringArrayValue(jsonNode.get(key)); - } - - return new String[0]; - - } /** - * Parses the response String to get the String Value of the key in jsonResponse. This method can be used for + * Parses the response String into {@link Document} type. This method can be used for * parsing the metadata in a String Json Format. * - * @param key The key / field to retrieve from jsonResponse - * @return Returns the String Value of the key in the jsonResponse . - * - * Returns Null in case the key is null or key doesn't exist in the json. + * @return Document Representation of the Metadata Response Body. + * @throws IOException in case parsing does not happen correctly. * *

* Example: *

      * {@code
      *
-     * Ec2Metadata ec2Metadata = Ec2Metadata.builder().build();
+     * Ec2Metadata ec2Metadata = Ec2Metadata.create();
      * MetadataResponse metadataResponse = client.get("/latest/dynamic/instance-identity/document");
-     * String region = metadataResponse.getStringArrayValuesFromJson("region");
+     * Document document = metadataResponse.asDocument();
      * }
      * 
*/ - public String getStringValueFromJson(String key) { + public Document asDocument() throws IOException { - if (null != key) { - - Map jsonNode = JSON_NODE_PARSER.parse(body).asObject(); - return stringValue(jsonNode.get(key)); - - } - - return null; + JsonNode node = JSON_NODE_PARSER.parse(body); + return node.visit(new DocumentUnmarshaller()); } - private String[] stringArrayValue(JsonNode jsonNode) { - if (null != jsonNode && jsonNode.isArray()) { - try { - return jsonNode.asArray() - .stream() - .filter(JsonNode::isString) - .map(JsonNode::asString) - .toArray(String[]::new); - } catch (Exception e) { - log.warn("Unable to parse jsonNode instance info : " + e.getMessage(), e); - } - } - return new String[0]; - } - - private String stringValue(JsonNode jsonNode) { - - if (null != jsonNode && jsonNode.isString()) { - try { - return jsonNode.asString(); - } catch (Exception e) { - log.warn("Unable to parse jsonNode instance info : " + e.getMessage(), e); - } - } - return null; - } } diff --git a/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java b/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java index 83a4bd1340d4..e41050922315 100644 --- a/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java +++ b/core/imds/src/test/java/software/amazon/awssdk/imds/MetadataResponseTest.java @@ -18,148 +18,104 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import software.amazon.awssdk.core.document.Document; /** * The class tests the utility methods provided by MetadataResponse Class . */ public class MetadataResponseTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test - public void get_devpayProductCodes_from_JsonResponse_success() throws IOException { + public void check_asString_success() throws IOException { - String jsonResponse = "{" - + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," - + "\"instanceType\":\"m1.small\"," - + "\"imageId\":\"ami-a49665cc\"," - + "\"instanceId\":\"i-6b2de041\"," - + "\"billingProducts\":[\"foo\"]," - + "\"architecture\":\"x86_64\"," - + "\"accountId\":\"599169622985\"," - + "\"kernelId\":\"aki-919dcaf8\"," - + "\"ramdiskId\":\"baz\"," - + "\"region\":\"us-east-1\"," - + "\"version\":\"2010-08-31\"," - + "\"availabilityZone\":\"us-east-1b\"," - + "\"privateIp\":\"10.201.215.38\"," - + "\"devpayProductCodes\":[\"bar\",\"foo\"]," - + "\"marketplaceProductCodes\":[\"qaz\"]" - + "}"; + String response = "foobar"; + + MetadataResponse metadataResponse = new MetadataResponse(response); + String result = metadataResponse.asString(); + assertThat(result).isEqualTo(response); - MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); - String[] result = metadataResponse.getStringArrayValuesFromJson("devpayProductCodes"); - assertThat(result).hasSize(2); } @Test - public void get_imageId_from_JsonResponse_success() throws IOException { + public void check_asString_failure() throws IOException { - String jsonResponse = "{" - + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," - + "\"instanceType\":\"m1.small\"," - + "\"imageId\":\"ami-a49665cc\"," - + "\"instanceId\":\"i-6b2de041\"," - + "\"billingProducts\":[\"foo\"]," - + "\"architecture\":\"x86_64\"," - + "\"accountId\":\"599169622985\"," - + "\"kernelId\":\"aki-919dcaf8\"," - + "\"ramdiskId\":\"baz\"," - + "\"region\":\"us-east-1\"," - + "\"version\":\"2010-08-31\"," - + "\"availabilityZone\":\"us-east-1b\"," - + "\"privateIp\":\"10.201.215.38\"," - + "\"devpayProductCodes\":[\"bar\"]," - + "\"marketplaceProductCodes\":[\"qaz\"]" - + "}"; + thrown.expect(NullPointerException.class); + thrown.expectMessage("Metadata is null"); - MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); - String result = metadataResponse.getStringValueFromJson("imageId"); - assertThat(result).isEqualTo("ami-a49665cc"); + MetadataResponse metadataResponse = new MetadataResponse(null); + String result = metadataResponse.asString(); } @Test - public void check_asString_success() throws IOException { + public void check_asList_success_with_delimiter() throws IOException { - String response = "foobar"; + String response = "sai\ntest"; MetadataResponse metadataResponse = new MetadataResponse(response); - String result = metadataResponse.asString(); - assertThat(result).isEqualTo(response); + List result = metadataResponse.asList(); + assertThat(result).hasSize(2); } @Test - public void check_asList_success() throws IOException { + public void check_asList_success_without_delimiter() throws IOException { - String response = "sai\ntest"; + String response = "test1-test2"; MetadataResponse metadataResponse = new MetadataResponse(response); List result = metadataResponse.asList(); - assertThat(result).hasSize(2); + assertThat(result).hasSize(1); } - @Test - public void get_devpayProductCodes_from_JsonResponse_failure() throws IOException { + public void check_asList_failure() throws IOException { - String jsonResponse = "{" - + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," - + "\"instanceType\":\"m1.small\"," - + "\"imageId\":\"ami-a49665cc\"," - + "\"instanceId\":\"i-6b2de041\"," - + "\"billingProducts\":[\"foo\"]," - + "\"architecture\":\"x86_64\"," - + "\"accountId\":\"599169622985\"," - + "\"kernelId\":\"aki-919dcaf8\"," - + "\"ramdiskId\":\"baz\"," - + "\"region\":\"us-east-1\"," - + "\"version\":\"2010-08-31\"," - + "\"availabilityZone\":\"us-east-1b\"," - + "\"privateIp\":\"10.201.215.38\"," - + "\"devpayProductCodes\":[\"bar\",\"foo\"]," - + "\"marketplaceProductCodes\":[\"qaz\"]" - + "}"; + thrown.expect(NullPointerException.class); + thrown.expectMessage("Metadata is null"); - MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); - String[] result = metadataResponse.getStringArrayValuesFromJson("devpayProductCodes1"); - assertThat(result).isNullOrEmpty(); + MetadataResponse metadataResponse = new MetadataResponse(null); + List result = metadataResponse.asList(); } @Test - public void get_imageId_from_JsonResponse_failure() throws IOException { + public void check_asDocument_failure() throws IOException { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Metadata is null"); - String jsonResponse = "{" - + "\"pendingTime\":\"2014-08-07T22:07:46Z\"," - + "\"instanceType\":\"m1.small\"," - + "\"imageId\":\"ami-a49665cc\"," - + "\"instanceId\":\"i-6b2de041\"," - + "\"billingProducts\":[\"foo\"]," - + "\"architecture\":\"x86_64\"," - + "\"accountId\":\"599169622985\"," - + "\"kernelId\":\"aki-919dcaf8\"," - + "\"ramdiskId\":\"baz\"," - + "\"region\":\"us-east-1\"," - + "\"version\":\"2010-08-31\"," - + "\"availabilityZone\":\"us-east-1b\"," - + "\"privateIp\":\"10.201.215.38\"," - + "\"devpayProductCodes\":[\"bar\",\"foo\"]," - + "\"marketplaceProductCodes\":[\"qaz\"]" - + "}"; + MetadataResponse metadataResponse = new MetadataResponse(null); + Document document = metadataResponse.asDocument(); - MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); - String result = metadataResponse.getStringValueFromJson("imageId1"); - assertThat(result).isNull(); } @Test - public void get_outputIamCredList_from_list_failure() throws IOException { + public void check_asDocument_success() throws IOException { + String jsonResponse = "{" + + "\"instanceType\":\"m1.small\"," + + "\"devpayProductCodes\":[\"bar\",\"foo\"]" + + "}"; - String response = "test1-test2"; + MetadataResponse metadataResponse = new MetadataResponse(jsonResponse); + Document document = metadataResponse.asDocument(); + Map expectedMap = new LinkedHashMap<>(); - MetadataResponse metadataResponse = new MetadataResponse(response); - List result = metadataResponse.asList(); - assertThat(result).isNullOrEmpty(); + List documentList = new ArrayList<>(); + documentList.add(Document.fromString("bar")); + documentList.add(Document.fromString("foo")); + expectedMap.put("instanceType", Document.fromString("m1.small")); + expectedMap.put("devpayProductCodes", Document.fromList(documentList)); + Document expectedDocumentMap = Document.fromMap(expectedMap); + assertThat(document).isEqualTo(expectedDocumentMap); } + } From 581a903ad9cc78590cf31d02d3140f44e3b59190 Mon Sep 17 00:00:00 2001 From: Sai Chandupatla Date: Thu, 14 Jul 2022 14:27:55 -0700 Subject: [PATCH 4/4] Changed dependency scope --- core/imds/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/imds/pom.xml b/core/imds/pom.xml index 90725f13a003..b0cfde3490a8 100644 --- a/core/imds/pom.xml +++ b/core/imds/pom.xml @@ -103,7 +103,7 @@ software.amazon.awssdk aws-json-protocol ${awsjavasdk.version} - test + compile software.amazon.awssdk