Skip to content

Commit 903df65

Browse files
authored
Add ipAddressType and mapper for endpointConfiguration
API Gateway REST APIs support an ipAddressType property on the endpoint configuration that controls whether an API accepts IPv4, IPv6, or both. The @endpointConfiguration trait did not model this property, and the trait had no OpenAPI mapper, so VPC endpoint IDs and the default-endpoint disable flag had to be set via jsonAdd workarounds. Add an ipAddressType string member to the trait with supported values `ipv4` and `dualstack`. Add the AddEndpointConfiguration OpenAPI mapper that writes vpcEndpointIds and disableExecuteApiEndpoint to the x-amazon-apigateway-endpoint-configuration extension. The types and ipAddressType members are omitted because they are configured outside of the OpenAPI document at API import time. Add a CloudFormation substitution path for vpcEndpointIds.
1 parent 182aae1 commit 903df65

17 files changed

Lines changed: 486 additions & 14 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "feature",
3+
"description": "Added `ipAddressType` field to the `aws.apigateway#endpointConfiguration` trait and added an OpenAPI mapper that writes `vpcEndpointIds` and `disableExecuteApiEndpoint` to the `x-amazon-apigateway-endpoint-configuration` extension.",
4+
"pull_requests": [
5+
"[#3110](https://github.com/smithy-lang/smithy/pull/3110)"
6+
]
7+
}

docs/source-2.0/aws/amazon-apigateway.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ Value type
466466
See also
467467
- `API endpoint types for REST APIs`_ for more information on
468468
endpoint types
469+
- `IP address types for REST APIs`_ for more information on the
470+
``ipAddressType`` property
469471
- `x-amazon-apigateway-endpoint-configuration`_ for the related
470472
OpenAPI extension
471473

@@ -490,6 +492,11 @@ supports the following members:
490492
- ``boolean``
491493
- Whether clients can invoke the API using the default
492494
``execute-api`` endpoint.
495+
* - ipAddressType
496+
- ``string`` enum (``ipv4`` or ``dualstack``)
497+
- The IP address type that can invoke the API. For the ``PRIVATE``
498+
endpoint type, only ``dualstack`` is supported. See
499+
`IP address types for REST APIs`_ for details.
493500

494501
The following example configures a private API with a VPC endpoint and
495502
disables the default endpoint:
@@ -506,6 +513,7 @@ disables the default endpoint:
506513
types: ["PRIVATE"]
507514
vpcEndpointIds: ["vpce-0212a4ababd5b8c3e"]
508515
disableExecuteApiEndpoint: true
516+
ipAddressType: "dualstack"
509517
)
510518
service Weather {
511519
version: "2018-03-17"
@@ -1267,4 +1275,5 @@ integration response to two ``header`` parameters of the method response.
12671275
.. _x-amazon-apigateway-policy: https://docs.aws.amazon.com/apigateway/latest/developerguide/openapi-extensions-policy.html
12681276
.. _x-amazon-apigateway-endpoint-configuration: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html
12691277
.. _API endpoint types for REST APIs: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html
1278+
.. _IP address types for REST APIs: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-ip-address-type.html
12701279
.. _endpoint IDs: https://docs.aws.amazon.com/vpc/latest/privatelink/concepts.html#concepts-vpc-endpoints

docs/source-2.0/guides/model-translations/converting-to-openapi.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,7 @@ uses the ``Fn::Sub`` variable syntax (``*`` means any value):
21222122
- ``paths/*/*/x-amazon-apigateway-integration/connectionId``
21232123
- ``paths/*/*/x-amazon-apigateway-integration/credentials``
21242124
- ``paths/*/*/x-amazon-apigateway-integration/uri``
2125+
- ``x-amazon-apigateway-endpoint-configuration/vpcEndpointIds/*``
21252126

21262127
.. note::
21272128

@@ -2359,6 +2360,58 @@ is converted to the following OpenAPI model:
23592360
}
23602361
23612362
2363+
.. _apigateway-endpoint-configuration:
2364+
2365+
Endpoint configuration
2366+
======================
2367+
2368+
The endpoint configuration for an API Gateway REST API can be set using the
2369+
:ref:`aws.apigateway#endpointConfiguration-trait`. Smithy writes the
2370+
``vpcEndpointIds`` and ``disableExecuteApiEndpoint`` values to the
2371+
`x-amazon-apigateway-endpoint-configuration`_ extension in the generated
2372+
OpenAPI document. The ``types`` and ``ipAddressType`` members are not part
2373+
of this extension and are configured outside of the OpenAPI document at
2374+
API import time.
2375+
2376+
The following Smithy model configures a private API with VPC endpoints and
2377+
disables the default ``execute-api`` endpoint:
2378+
2379+
.. code-block:: smithy
2380+
2381+
$version: "2"
2382+
namespace smithy.example
2383+
2384+
use aws.apigateway#endpointConfiguration
2385+
use aws.protocols#restJson1
2386+
2387+
@restJson1
2388+
@endpointConfiguration(
2389+
types: ["PRIVATE"]
2390+
vpcEndpointIds: ["vpce-0212a4ababd5b8c3e"]
2391+
disableExecuteApiEndpoint: true
2392+
ipAddressType: "dualstack"
2393+
)
2394+
service Example {
2395+
version: "2019-06-17"
2396+
}
2397+
2398+
is converted to the following OpenAPI model:
2399+
2400+
.. code-block:: json
2401+
2402+
{
2403+
"openapi": "3.0.2",
2404+
"info": {
2405+
"title": "Example",
2406+
"version": "2019-06-17"
2407+
},
2408+
"x-amazon-apigateway-endpoint-configuration": {
2409+
"vpcEndpointIds": ["vpce-0212a4ababd5b8c3e"],
2410+
"disableExecuteApiEndpoint": true
2411+
}
2412+
}
2413+
2414+
23622415
.. _other-traits:
23632416

23642417
Other traits that influence API Gateway
@@ -2465,3 +2518,4 @@ The conversion process is highly extensible through
24652518
.. _x-amazon-apigateway-gateway-responses: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-gateway-responses.html
24662519
.. _x-amazon-apigateway-security-policy: https://docs.aws.amazon.com/apigateway/latest/developerguide/openapi-extensions-security-policy.html
24672520
.. _x-amazon-apigateway-endpoint-access-mode: https://docs.aws.amazon.com/apigateway/latest/developerguide/openapi-extensions-endpoint-access-mode.html
2521+
.. _x-amazon-apigateway-endpoint-configuration: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.aws.apigateway.openapi;
6+
7+
import java.util.List;
8+
import java.util.logging.Logger;
9+
import software.amazon.smithy.aws.apigateway.traits.EndpointConfigurationTrait;
10+
import software.amazon.smithy.model.node.ArrayNode;
11+
import software.amazon.smithy.model.node.Node;
12+
import software.amazon.smithy.model.node.ObjectNode;
13+
import software.amazon.smithy.model.traits.Trait;
14+
import software.amazon.smithy.openapi.fromsmithy.Context;
15+
import software.amazon.smithy.openapi.model.OpenApi;
16+
import software.amazon.smithy.utils.ListUtils;
17+
18+
/**
19+
* Adds the API Gateway {@code x-amazon-apigateway-endpoint-configuration}
20+
* extension to the OpenAPI model when the {@link EndpointConfigurationTrait}
21+
* is applied to a service.
22+
*
23+
* <p>The {@code types} member is not written to the extension because it is
24+
* not a supported property of {@code x-amazon-apigateway-endpoint-configuration};
25+
* endpoint types are configured outside of the OpenAPI extension at API import
26+
* time.
27+
*
28+
* <p>The {@code ipAddressType} member is also omitted from the extension
29+
* because it is not a supported property; it is configured outside of the
30+
* OpenAPI extension at API import time.
31+
*
32+
* @see <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html">x-amazon-apigateway-endpoint-configuration</a>
33+
*/
34+
final class AddEndpointConfiguration implements ApiGatewayMapper {
35+
36+
private static final String EXTENSION_NAME = "x-amazon-apigateway-endpoint-configuration";
37+
private static final String VPC_ENDPOINT_IDS = "vpcEndpointIds";
38+
private static final String DISABLE_EXECUTE_API_ENDPOINT = "disableExecuteApiEndpoint";
39+
private static final Logger LOGGER = Logger.getLogger(AddEndpointConfiguration.class.getName());
40+
41+
@Override
42+
public List<ApiGatewayConfig.ApiType> getApiTypes() {
43+
return ListUtils.of(ApiGatewayConfig.ApiType.REST);
44+
}
45+
46+
@Override
47+
public OpenApi after(Context<? extends Trait> context, OpenApi openApi) {
48+
return context.getService()
49+
.getTrait(EndpointConfigurationTrait.class)
50+
.map(trait -> addExtension(context, openApi, trait))
51+
.orElse(openApi);
52+
}
53+
54+
private OpenApi addExtension(
55+
Context<? extends Trait> context,
56+
OpenApi openApi,
57+
EndpointConfigurationTrait trait
58+
) {
59+
ObjectNode.Builder node = Node.objectNodeBuilder();
60+
61+
trait.getVpcEndpointIds()
62+
.ifPresent(ids -> node.withMember(
63+
VPC_ENDPOINT_IDS,
64+
ids.stream().map(Node::from).collect(ArrayNode.collect())));
65+
66+
trait.getDisableExecuteApiEndpoint()
67+
.ifPresent(disabled -> node.withMember(
68+
DISABLE_EXECUTE_API_ENDPOINT,
69+
Node.from(disabled)));
70+
71+
ObjectNode extension = node.build();
72+
if (extension.isEmpty()) {
73+
return openApi;
74+
}
75+
76+
LOGGER.fine(() -> String.format(
77+
"Adding %s to %s",
78+
EXTENSION_NAME,
79+
context.getService().getId()));
80+
81+
return openApi.toBuilder().putExtension(EXTENSION_NAME, extension).build();
82+
}
83+
}

smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/ApiGatewayExtension.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public List<OpenApiMapper> getOpenApiMappers() {
2323
ApiGatewayMapper.wrap(new AddAuthorizers()),
2424
ApiGatewayMapper.wrap(new AddBinaryTypes()),
2525
ApiGatewayMapper.wrap(new AddCognitoUserPoolsScopes()),
26+
ApiGatewayMapper.wrap(new AddEndpointConfiguration()),
2627
ApiGatewayMapper.wrap(new AddIntegrations()),
2728
ApiGatewayMapper.wrap(new AddMinimumCompressionSize()),
2829

smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/CloudFormationSubstitution.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ final class CloudFormationSubstitution implements ApiGatewayMapper {
5151
"paths/*/*/x-amazon-apigateway-integration/connectionId",
5252
"paths/*/*/x-amazon-apigateway-integration/credentials",
5353
"paths/*/*/x-amazon-apigateway-integration/uri",
54-
"paths/*/*/x-amazon-apigateway-integration/integrationTarget");
54+
"paths/*/*/x-amazon-apigateway-integration/integrationTarget",
55+
"x-amazon-apigateway-endpoint-configuration/vpcEndpointIds/*");
5556

5657
@Override
5758
public List<ApiGatewayConfig.ApiType> getApiTypes() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.aws.apigateway.openapi;
6+
7+
import static org.hamcrest.MatcherAssert.assertThat;
8+
import static org.hamcrest.Matchers.contains;
9+
import static org.hamcrest.Matchers.equalTo;
10+
import static org.hamcrest.Matchers.is;
11+
12+
import java.util.stream.Collectors;
13+
import org.junit.jupiter.api.Test;
14+
import software.amazon.smithy.model.Model;
15+
import software.amazon.smithy.model.node.ObjectNode;
16+
import software.amazon.smithy.model.node.StringNode;
17+
import software.amazon.smithy.model.shapes.ShapeId;
18+
import software.amazon.smithy.openapi.OpenApiConfig;
19+
import software.amazon.smithy.openapi.fromsmithy.OpenApiConverter;
20+
import software.amazon.smithy.openapi.model.OpenApi;
21+
22+
public class AddEndpointConfigurationTest {
23+
private static final String EXTENSION_NAME = "x-amazon-apigateway-endpoint-configuration";
24+
25+
@Test
26+
public void addsExtensionWithVpcEndpointIdsAndDisableFlag() {
27+
OpenApi result = convert("endpoint-configuration.smithy");
28+
29+
assertThat(result.getExtension(EXTENSION_NAME).isPresent(), is(true));
30+
ObjectNode extension = result.getExtension(EXTENSION_NAME).get().expectObjectNode();
31+
32+
assertThat(extension.expectArrayMember("vpcEndpointIds")
33+
.getElements()
34+
.stream()
35+
.map(node -> ((StringNode) node).getValue())
36+
.collect(Collectors.toList()),
37+
contains("vpce-0212a4ababd5b8c3e", "vpce-01d622316a7df47f9"));
38+
assertThat(extension.expectBooleanMember("disableExecuteApiEndpoint").getValue(),
39+
equalTo(true));
40+
41+
// types and ipAddressType are not part of the extension.
42+
assertThat(extension.getMember("types").isPresent(), is(false));
43+
assertThat(extension.getMember("ipAddressType").isPresent(), is(false));
44+
}
45+
46+
@Test
47+
public void omitsExtensionWhenOnlyTypesIsSet() {
48+
OpenApi result = convert("endpoint-configuration-minimal.smithy");
49+
50+
// Only types is set on the trait, and types is not part of the
51+
// extension. The mapper must not emit an empty extension.
52+
assertThat(result.getExtension(EXTENSION_NAME).isPresent(), is(false));
53+
}
54+
55+
@Test
56+
public void wrapsVpcEndpointIdCloudFormationVariablesInFnSub() {
57+
// The CFN substitution runs in updateNode, so convert to node to
58+
// pick up Fn::Sub wrapping.
59+
Model assembled = Model.assembler()
60+
.discoverModels(getClass().getClassLoader())
61+
.addImport(getClass().getResource("endpoint-configuration-cfn.smithy"))
62+
.assemble()
63+
.unwrap();
64+
OpenApiConfig config = new OpenApiConfig();
65+
config.setService(ShapeId.from("smithy.example#Service"));
66+
ObjectNode result = OpenApiConverter.create()
67+
.config(config)
68+
.classLoader(getClass().getClassLoader())
69+
.convertToNode(assembled);
70+
71+
ObjectNode extension = result.expectObjectMember(EXTENSION_NAME);
72+
ObjectNode firstId = extension.expectArrayMember("vpcEndpointIds")
73+
.get(0)
74+
.get()
75+
.expectObjectNode();
76+
77+
assertThat(firstId.expectStringMember("Fn::Sub").getValue(),
78+
equalTo("${MyVpcEndpointId}"));
79+
}
80+
81+
private OpenApi convert(String model) {
82+
Model assembled = Model.assembler()
83+
.discoverModels(getClass().getClassLoader())
84+
.addImport(getClass().getResource(model))
85+
.assemble()
86+
.unwrap();
87+
OpenApiConfig config = new OpenApiConfig();
88+
config.setService(ShapeId.from("smithy.example#Service"));
89+
return OpenApiConverter.create()
90+
.config(config)
91+
.classLoader(getClass().getClassLoader())
92+
.convert(assembled);
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
use aws.apigateway#endpointConfiguration
6+
use aws.protocols#restJson1
7+
8+
@restJson1
9+
@endpointConfiguration(
10+
types: ["PRIVATE"]
11+
vpcEndpointIds: ["${MyVpcEndpointId}"]
12+
disableExecuteApiEndpoint: true
13+
)
14+
service Service {
15+
version: "2006-03-01"
16+
operations: [Operation]
17+
}
18+
19+
@http(uri: "/", method: "GET")
20+
operation Operation {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
use aws.apigateway#endpointConfiguration
6+
use aws.protocols#restJson1
7+
8+
/// Only required `types` is set. No extension should be emitted because
9+
/// `types` and `ipAddressType` are not written to
10+
/// `x-amazon-apigateway-endpoint-configuration`.
11+
@restJson1
12+
@endpointConfiguration(types: ["REGIONAL"])
13+
service Service {
14+
version: "2006-03-01"
15+
operations: [Operation]
16+
}
17+
18+
@http(uri: "/", method: "GET")
19+
operation Operation {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
use aws.apigateway#endpointConfiguration
6+
use aws.protocols#restJson1
7+
8+
@restJson1
9+
@endpointConfiguration(
10+
types: ["PRIVATE"]
11+
vpcEndpointIds: ["vpce-0212a4ababd5b8c3e", "vpce-01d622316a7df47f9"]
12+
disableExecuteApiEndpoint: true
13+
ipAddressType: "dualstack"
14+
)
15+
service Service {
16+
version: "2006-03-01"
17+
operations: [Operation]
18+
}
19+
20+
@http(uri: "/", method: "GET")
21+
operation Operation {}

0 commit comments

Comments
 (0)