Skip to content

Commit

Permalink
Add service-managed egress IP address properties (aws-cloudformation#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
aws-sde authored Jan 19, 2024
1 parent fe04c1c commit ceb7bf2
Show file tree
Hide file tree
Showing 12 changed files with 72 additions and 14 deletions.
11 changes: 10 additions & 1 deletion aws-transfer-connector/aws-transfer-connector.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@
"minLength": 20,
"maxLength": 2048
},
"ServiceManagedEgressIpAddresses": {
"description": "The list of egress IP addresses of this connector. These IP addresses are assigned automatically when you create the connector.",
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$"
}
},
"Tags": {
"description": "Key-value pairs that can be used to group and search for connectors. Tags are metadata attached to connectors for any purpose.",
"type": "array",
Expand All @@ -190,7 +198,8 @@
],
"readOnlyProperties": [
"/properties/Arn",
"/properties/ConnectorId"
"/properties/ConnectorId",
"/properties/ServiceManagedEgressIpAddresses"
],
"primaryIdentifier": [
"/properties/ConnectorId"
Expand Down
4 changes: 4 additions & 0 deletions aws-transfer-connector/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ Specifies the unique Amazon Resource Name (ARN) for the connector.

A unique identifier for the connector.

#### ServiceManagedEgressIpAddresses

The list of egress IP addresses of this connector. These IP addresses are assigned automatically when you create the connector.

2 changes: 1 addition & 1 deletion aws-transfer-connector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>transfer</artifactId>
<version>2.21.45</version>
<version>2.23.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
: null)
.connectorId(describedConnector.connectorId())
.loggingRole(describedConnector.loggingRole())
.serviceManagedEgressIpAddresses(describedConnector.serviceManagedEgressIpAddresses())
.tags(
(CollectionUtils.isNullOrEmpty(describedConnector.tags()))
? null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import static org.mockito.Mockito.mock;
import static software.amazon.transfer.connector.AbstractTestBase.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -55,8 +57,13 @@ public void setup() {
public void handleRequest_SimpleSuccess() {
final ReadHandler handler = new ReadHandler(client);

final ResourceModel model =
ResourceModel.builder().connectorId(TEST_CONNECTOR_ID).build();
final List<String> addresses = List.of("0.0.0.0", "1.1.1.1", "2.2.2.2");
final ResourceModel model = ResourceModel.builder()
.accessRole(TEST_ACCESS_ROLE)
.connectorId(TEST_CONNECTOR_ID)
.loggingRole(TEST_LOGGING_ROLE)
.serviceManagedEgressIpAddresses(addresses)
.build();

final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
.desiredResourceState(model)
Expand All @@ -65,21 +72,21 @@ public void handleRequest_SimpleSuccess() {
DescribeConnectorResponse describeConnectorResponse = DescribeConnectorResponse.builder()
.connector(DescribedConnector.builder()
.accessRole(TEST_ACCESS_ROLE)
.connectorId(TEST_CONNECTOR_ID)
.loggingRole(TEST_LOGGING_ROLE)
.serviceManagedEgressIpAddresses(addresses)
.build())
.build();
doReturn(describeConnectorResponse).when(proxy).injectCredentialsAndInvokeV2(any(), any());

final ProgressEvent<ResourceModel, CallbackContext> response =
handler.handleRequest(proxy, request, null, logger);
ResourceModel testModel = response.getResourceModel();

assertThat(response).isNotNull();
assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
assertThat(response.getCallbackContext()).isNull();
assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
assertThat(testModel).hasFieldOrPropertyWithValue("accessRole", TEST_ACCESS_ROLE);
assertThat(testModel).hasFieldOrPropertyWithValue("loggingRole", TEST_LOGGING_ROLE);
assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
assertThat(response.getResourceModels()).isNull();
assertThat(response.getMessage()).isNull();
assertThat(response.getErrorCode()).isNull();
Expand Down
11 changes: 10 additions & 1 deletion aws-transfer-server/aws-transfer-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@
"minLength": 20,
"pattern": "^arn:"
},
"As2ServiceManagedEgressIpAddresses": {
"description": "The list of egress IP addresses of this server. These IP addresses are only relevant for servers that use the AS2 protocol. They are used for sending asynchronous MDNs. These IP addresses are assigned automatically when you create an AS2 server. Additionally, if you update an existing server and add the AS2 protocol, static IP addresses are assigned as well.",
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$"
}
},
"Certificate": {
"type": "string",
"maxLength": 1600,
Expand Down Expand Up @@ -342,7 +350,8 @@
],
"readOnlyProperties": [
"/properties/Arn",
"/properties/ServerId"
"/properties/ServerId",
"/properties/As2ServiceManagedEgressIpAddresses"
],
"handlers": {
"create": {
Expand Down
4 changes: 4 additions & 0 deletions aws-transfer-server/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,7 @@ Returns the <code>Arn</code> value.

Returns the <code>ServerId</code> value.

#### As2ServiceManagedEgressIpAddresses

The list of egress IP addresses of this server. These IP addresses are only relevant for servers that use the AS2 protocol. They are used for sending asynchronous MDNs. These IP addresses are assigned automatically when you create an AS2 server. Additionally, if you update an existing server and add the AS2 protocol, static IP addresses are assigned as well.

4 changes: 2 additions & 2 deletions aws-transfer-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>transfer</artifactId>
<version>2.21.45</version>
<version>2.23.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/ec2 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ec2</artifactId>
<version>2.21.45</version>
<version>2.23.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ private ResourceModel translateFromReadResponse(final DescribeServerResponse res
DescribedServer server = response.server();
return ResourceModel.builder()
.arn(server.arn())
// For non AS2-servers, our API returns null for this property.
// However, the AWS SDK will never return null for a map or collection property in a response.
// Contract tests require all read-only properties to be returned by a READ request,
// so we cannot mimic our API's behaviour here. Thus, we return an empty list instead of null.
.as2ServiceManagedEgressIpAddresses(server.as2ServiceManagedEgressIpAddresses())
.serverId(server.serverId())
.certificate(server.certificate())
.domain(server.domainAsString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ protected static DescribeServerResponse describeServerFromModel(
return DescribeServerResponse.builder()
.server(DescribedServer.builder()
.arn(getTestServerArn(serverId))
.as2ServiceManagedEgressIpAddresses(model.getAs2ServiceManagedEgressIpAddresses())
.serverId(serverId)
.state(state)
.domain(model.getDomain())
Expand Down Expand Up @@ -182,13 +183,15 @@ protected static ResourceModel setupSimpleServerModel(String endpointType) {
endpointDetails = getEndpointDetails(Collections.emptyList());
}
return ResourceModel.builder()
.as2ServiceManagedEgressIpAddresses(Collections.emptyList())
.domain(Domain.S3.name())
.endpointType(endpointType)
.endpointDetails(endpointDetails)
.identityProviderType(DEFAULT_IDENTITY_PROVIDER_TYPE)
.securityPolicyName(DEFAULT_SECURITY_POLICY)
.protocols(DEFAULT_PROTOCOLS)
.structuredLogDestinations(Collections.emptyList())
.tags(Collections.emptyList())
.build();
}

Expand Down Expand Up @@ -222,6 +225,7 @@ protected static ResourceModel fullyLoadedServerModel() {
EndpointDetails endpointDetails = getEndpointDetails(Arrays.asList("addr1", "addr2"));

return ResourceModel.builder()
.as2ServiceManagedEgressIpAddresses(List.of("0.0.0.0", "1.1.1.1", "2.2.2.2"))
.certificate("certificate")
.domain(Domain.S3.name())
.endpointType(EndpointType.VPC.name())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;

import java.util.stream.Stream;

import org.assertj.core.api.SoftAssertions;
import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;

import software.amazon.awssdk.services.ec2.model.DescribeVpcEndpointsRequest;
import software.amazon.awssdk.services.ec2.model.DescribeVpcEndpointsResponse;
import software.amazon.awssdk.services.ec2.model.State;
import software.amazon.awssdk.services.transfer.model.DescribeServerRequest;
import software.amazon.awssdk.services.transfer.model.DescribeServerResponse;
import software.amazon.awssdk.services.transfer.model.EndpointType;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
Expand All @@ -27,11 +31,11 @@ public class ReadHandlerTest extends AbstractTestBase {
@InjectSoftAssertions
private SoftAssertions softly;

@Test
public void handleRequest_SimpleSuccess() {
@ParameterizedTest
@MethodSource
public void handleRequest_SimpleSuccess(ResourceModel model) {
final ReadHandler handler = new ReadHandler();

ResourceModel model = fullyLoadedServerModel();
model.setArn(getTestServerArn("testServer"));
model.setServerId("testServer");

Expand Down Expand Up @@ -59,4 +63,8 @@ public void handleRequest_SimpleSuccess() {
softly.assertThat(response.getMessage()).isNull();
softly.assertThat(response.getErrorCode()).isNull();
}

private static Stream<ResourceModel> handleRequest_SimpleSuccess() {
return Stream.of(fullyLoadedServerModel(), setupSimpleServerModel(EndpointType.VPC.name()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ public void stabilizationFailures() {

private ProgressEvent<ResourceModel, CallbackContext> updateServerAndAssertStatus(
ResourceHandlerRequest<ResourceModel> request, String postUpdateState, OperationStatus operationStatus) {
// Create a copy to avoid the update handler mutating the original.
request = request.toBuilder()
.previousResourceState(
request.getPreviousResourceState().toBuilder().build())
.desiredResourceState(
request.getDesiredResourceState().toBuilder().build())
.build();

ResourceModel currentState = request.getPreviousResourceState();
ResourceModel desiredState = request.getDesiredResourceState();
Expand Down

0 comments on commit ceb7bf2

Please sign in to comment.