diff --git a/.changelog/27368.txt b/.changelog/27368.txt new file mode 100644 index 00000000000..470d5e20258 --- /dev/null +++ b/.changelog/27368.txt @@ -0,0 +1,19 @@ +```release-note:enhancement +resource/aws_appmesh_virtual_node: Allow for multiple `listener` blocks +``` + +```release-note:enhancement +resource/aws_appmesh_virtual_router: Allow for multiple `listener` blocks +``` + +```release-note:enhancement +resource/aws_appmesh_virtual_gateway: Allow for multiple `listener` blocks +``` + +```release-note:enhancement +resource/aws_appmesh_gateway_route: Add `port` to `target` block, which is required when target Virtual Nodes or Virtual Routers have multiple listeners +``` + +```release-note:enhancement +resource/aws_appmesh_route: Add `port` to `weighted_target` block, which is required when target Virtual Nodes have multiple listeners +``` diff --git a/internal/service/appmesh/appmesh_test.go b/internal/service/appmesh/appmesh_test.go index 995b3f5a59c..c9dffeaf68a 100644 --- a/internal/service/appmesh/appmesh_test.go +++ b/internal/service/appmesh/appmesh_test.go @@ -20,19 +20,23 @@ func TestAccAppMesh_serial(t *testing.T) { "tags": testAccMesh_tags, }, "Route": { - "grpcRoute": testAccRoute_grpcRoute, - "grpcRouteEmptyMatch": testAccRoute_grpcRouteEmptyMatch, - "grpcRouteTimeout": testAccRoute_grpcRouteTimeout, - "http2Route": testAccRoute_http2Route, - "http2RouteTimeout": testAccRoute_http2RouteTimeout, - "httpHeader": testAccRoute_httpHeader, - "httpRetryPolicy": testAccRoute_httpRetryPolicy, - "httpRoute": testAccRoute_httpRoute, - "httpRouteTimeout": testAccRoute_httpRouteTimeout, - "routePriority": testAccRoute_routePriority, - "tcpRoute": testAccRoute_tcpRoute, - "tcpRouteTimeout": testAccRoute_tcpRouteTimeout, - "tags": testAccRoute_tags, + "grpcRoute": testAccRoute_grpcRoute, + "grpcRouteEmptyMatch": testAccRoute_grpcRouteEmptyMatch, + "grpcRouteTimeout": testAccRoute_grpcRouteTimeout, + "grpcRouteTargetPort": testAccRoute_grpcRouteTargetPort, + "http2Route": testAccRoute_http2Route, + "http2RouteTimeout": testAccRoute_http2RouteTimeout, + "http2RouteTargetPort": testAccRoute_http2RouteTargetPort, + "httpHeader": testAccRoute_httpHeader, + "httpRetryPolicy": testAccRoute_httpRetryPolicy, + "httpRoute": testAccRoute_httpRoute, + "httpRouteTimeout": testAccRoute_httpRouteTimeout, + "httpRouteTargetPort": testAccRoute_httpRouteTargetPort, + "routePriority": testAccRoute_routePriority, + "tcpRoute": testAccRoute_tcpRoute, + "tcpRouteTimeout": testAccRoute_tcpRouteTimeout, + "tcpRouteTargetPort": testAccRoute_tcpRouteTargetPort, + "tags": testAccRoute_tags, }, "VirtualGateway": { "basic": testAccVirtualGateway_basic, @@ -43,6 +47,7 @@ func TestAccAppMesh_serial(t *testing.T) { "listenerHealthChecks": testAccVirtualGateway_ListenerHealthChecks, "listenerTls": testAccVirtualGateway_ListenerTLS, "listenerValidation": testAccVirtualGateway_ListenerValidation, + "multipleListeners": testAccVirtualGateway_multipleListeners, "logging": testAccVirtualGateway_Logging, "tags": testAccVirtualGateway_Tags, }, @@ -60,12 +65,14 @@ func TestAccAppMesh_serial(t *testing.T) { "listenerTimeout": testAccVirtualNode_listenerTimeout, "listenerTls": testAccVirtualNode_listenerTLS, "listenerValidation": testAccVirtualNode_listenerValidation, + "multipleListeners": testAccVirtualNode_multipleListeners, "logging": testAccVirtualNode_logging, "tags": testAccVirtualNode_tags, }, "VirtualRouter": { - "basic": testAccVirtualRouter_basic, - "tags": testAccVirtualRouter_tags, + "basic": testAccVirtualRouter_basic, + "multipleListeners": testAccVirtualRouter_multipleListeners, + "tags": testAccVirtualRouter_tags, }, "VirtualService": { "virtualNode": testAccVirtualService_virtualNode, diff --git a/internal/service/appmesh/flex.go b/internal/service/appmesh/flex.go index 95a83893dce..31f207f4ac4 100644 --- a/internal/service/appmesh/flex.go +++ b/internal/service/appmesh/flex.go @@ -187,6 +187,9 @@ func expandGRPCRoute(vGrpcRoute []interface{}) *appmesh.GrpcRoute { if vWeight, ok := mWeightedTarget["weight"].(int); ok { weightedTarget.Weight = aws.Int64(int64(vWeight)) } + if vPort, ok := mWeightedTarget["port"].(int); ok { + weightedTarget.Port = aws.Int64(int64(vPort)) + } weightedTargets = append(weightedTargets, weightedTarget) } @@ -351,6 +354,9 @@ func expandHTTPRoute(vHttpRoute []interface{}) *appmesh.HttpRoute { if vWeight, ok := mWeightedTarget["weight"].(int); ok { weightedTarget.Weight = aws.Int64(int64(vWeight)) } + if vPort, ok := mWeightedTarget["port"].(int); ok { + weightedTarget.Port = aws.Int64(int64(vPort)) + } weightedTargets = append(weightedTargets, weightedTarget) } @@ -563,6 +569,9 @@ func expandTCPRoute(vTcpRoute []interface{}) *appmesh.TcpRoute { if vWeight, ok := mWeightedTarget["weight"].(int); ok { weightedTarget.Weight = aws.Int64(int64(vWeight)) } + if vPort, ok := mWeightedTarget["port"].(int); ok { + weightedTarget.Port = aws.Int64(int64(vPort)) + } weightedTargets = append(weightedTargets, weightedTarget) } @@ -1204,6 +1213,7 @@ func flattenGRPCRoute(grpcRoute *appmesh.GrpcRoute) []interface{} { mWeightedTarget := map[string]interface{}{ "virtual_node": aws.StringValue(weightedTarget.VirtualNode), "weight": int(aws.Int64Value(weightedTarget.Weight)), + "port": int(aws.Int64Value(weightedTarget.Port)), } vWeightedTargets = append(vWeightedTargets, mWeightedTarget) @@ -1303,6 +1313,7 @@ func flattenHTTPRoute(httpRoute *appmesh.HttpRoute) []interface{} { mWeightedTarget := map[string]interface{}{ "virtual_node": aws.StringValue(weightedTarget.VirtualNode), "weight": int(aws.Int64Value(weightedTarget.Weight)), + "port": int(aws.Int64Value(weightedTarget.Port)), } vWeightedTargets = append(vWeightedTargets, mWeightedTarget) @@ -1436,6 +1447,7 @@ func flattenTCPRoute(tcpRoute *appmesh.TcpRoute) []interface{} { mWeightedTarget := map[string]interface{}{ "virtual_node": aws.StringValue(weightedTarget.VirtualNode), "weight": int(aws.Int64Value(weightedTarget.Weight)), + "port": int(aws.Int64Value(weightedTarget.Port)), } vWeightedTargets = append(vWeightedTargets, mWeightedTarget) @@ -1502,169 +1514,173 @@ func flattenVirtualNodeSpec(spec *appmesh.VirtualNodeSpec) []interface{} { mSpec["backend_defaults"] = []interface{}{mBackendDefaults} } - if spec.Listeners != nil && spec.Listeners[0] != nil { - // Per schema definition, set at most 1 Listener - listener := spec.Listeners[0] - mListener := map[string]interface{}{} + if listeners := spec.Listeners; listeners != nil && spec.Listeners[0] != nil { + vListeners := []interface{}{} - if connectionPool := listener.ConnectionPool; connectionPool != nil { - mConnectionPool := map[string]interface{}{} + for _, listener := range listeners { + mListener := map[string]interface{}{} - if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { - mGrpcConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), - } - mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} - } + if connectionPool := listener.ConnectionPool; connectionPool != nil { + mConnectionPool := map[string]interface{}{} - if httpConnectionPool := connectionPool.Http; httpConnectionPool != nil { - mHttpConnectionPool := map[string]interface{}{ - "max_connections": int(aws.Int64Value(httpConnectionPool.MaxConnections)), - "max_pending_requests": int(aws.Int64Value(httpConnectionPool.MaxPendingRequests)), + if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { + mGrpcConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), + } + mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} } - mConnectionPool["http"] = []interface{}{mHttpConnectionPool} - } - if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { - mHttp2ConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + if httpConnectionPool := connectionPool.Http; httpConnectionPool != nil { + mHttpConnectionPool := map[string]interface{}{ + "max_connections": int(aws.Int64Value(httpConnectionPool.MaxConnections)), + "max_pending_requests": int(aws.Int64Value(httpConnectionPool.MaxPendingRequests)), + } + mConnectionPool["http"] = []interface{}{mHttpConnectionPool} } - mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} - } - if tcpConnectionPool := connectionPool.Tcp; tcpConnectionPool != nil { - mTcpConnectionPool := map[string]interface{}{ - "max_connections": int(aws.Int64Value(tcpConnectionPool.MaxConnections)), + if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { + mHttp2ConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + } + mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} } - mConnectionPool["tcp"] = []interface{}{mTcpConnectionPool} - } - mListener["connection_pool"] = []interface{}{mConnectionPool} - } + if tcpConnectionPool := connectionPool.Tcp; tcpConnectionPool != nil { + mTcpConnectionPool := map[string]interface{}{ + "max_connections": int(aws.Int64Value(tcpConnectionPool.MaxConnections)), + } + mConnectionPool["tcp"] = []interface{}{mTcpConnectionPool} + } - if healthCheck := listener.HealthCheck; healthCheck != nil { - mHealthCheck := map[string]interface{}{ - "healthy_threshold": int(aws.Int64Value(healthCheck.HealthyThreshold)), - "interval_millis": int(aws.Int64Value(healthCheck.IntervalMillis)), - "path": aws.StringValue(healthCheck.Path), - "port": int(aws.Int64Value(healthCheck.Port)), - "protocol": aws.StringValue(healthCheck.Protocol), - "timeout_millis": int(aws.Int64Value(healthCheck.TimeoutMillis)), - "unhealthy_threshold": int(aws.Int64Value(healthCheck.UnhealthyThreshold)), + mListener["connection_pool"] = []interface{}{mConnectionPool} } - mListener["health_check"] = []interface{}{mHealthCheck} - } - if outlierDetection := listener.OutlierDetection; outlierDetection != nil { - mOutlierDetection := map[string]interface{}{ - "base_ejection_duration": flattenDuration(outlierDetection.BaseEjectionDuration), - "interval": flattenDuration(outlierDetection.Interval), - "max_ejection_percent": int(aws.Int64Value(outlierDetection.MaxEjectionPercent)), - "max_server_errors": int(aws.Int64Value(outlierDetection.MaxServerErrors)), + if healthCheck := listener.HealthCheck; healthCheck != nil { + mHealthCheck := map[string]interface{}{ + "healthy_threshold": int(aws.Int64Value(healthCheck.HealthyThreshold)), + "interval_millis": int(aws.Int64Value(healthCheck.IntervalMillis)), + "path": aws.StringValue(healthCheck.Path), + "port": int(aws.Int64Value(healthCheck.Port)), + "protocol": aws.StringValue(healthCheck.Protocol), + "timeout_millis": int(aws.Int64Value(healthCheck.TimeoutMillis)), + "unhealthy_threshold": int(aws.Int64Value(healthCheck.UnhealthyThreshold)), + } + mListener["health_check"] = []interface{}{mHealthCheck} } - mListener["outlier_detection"] = []interface{}{mOutlierDetection} - } - if portMapping := listener.PortMapping; portMapping != nil { - mPortMapping := map[string]interface{}{ - "port": int(aws.Int64Value(portMapping.Port)), - "protocol": aws.StringValue(portMapping.Protocol), + if outlierDetection := listener.OutlierDetection; outlierDetection != nil { + mOutlierDetection := map[string]interface{}{ + "base_ejection_duration": flattenDuration(outlierDetection.BaseEjectionDuration), + "interval": flattenDuration(outlierDetection.Interval), + "max_ejection_percent": int(aws.Int64Value(outlierDetection.MaxEjectionPercent)), + "max_server_errors": int(aws.Int64Value(outlierDetection.MaxServerErrors)), + } + mListener["outlier_detection"] = []interface{}{mOutlierDetection} } - mListener["port_mapping"] = []interface{}{mPortMapping} - } - if listenerTimeout := listener.Timeout; listenerTimeout != nil { - mListenerTimeout := map[string]interface{}{ - "grpc": flattenGRPCTimeout(listenerTimeout.Grpc), - "http": flattenHTTPTimeout(listenerTimeout.Http), - "http2": flattenHTTPTimeout(listenerTimeout.Http2), - "tcp": flattenTCPTimeout(listenerTimeout.Tcp), + if portMapping := listener.PortMapping; portMapping != nil { + mPortMapping := map[string]interface{}{ + "port": int(aws.Int64Value(portMapping.Port)), + "protocol": aws.StringValue(portMapping.Protocol), + } + mListener["port_mapping"] = []interface{}{mPortMapping} } - mListener["timeout"] = []interface{}{mListenerTimeout} - } - if tls := listener.Tls; tls != nil { - mTls := map[string]interface{}{ - "mode": aws.StringValue(tls.Mode), + if listenerTimeout := listener.Timeout; listenerTimeout != nil { + mListenerTimeout := map[string]interface{}{ + "grpc": flattenGRPCTimeout(listenerTimeout.Grpc), + "http": flattenHTTPTimeout(listenerTimeout.Http), + "http2": flattenHTTPTimeout(listenerTimeout.Http2), + "tcp": flattenTCPTimeout(listenerTimeout.Tcp), + } + mListener["timeout"] = []interface{}{mListenerTimeout} } - if certificate := tls.Certificate; certificate != nil { - mCertificate := map[string]interface{}{} + if tls := listener.Tls; tls != nil { + mTls := map[string]interface{}{ + "mode": aws.StringValue(tls.Mode), + } - if acm := certificate.Acm; acm != nil { - mAcm := map[string]interface{}{ - "certificate_arn": aws.StringValue(acm.CertificateArn), + if certificate := tls.Certificate; certificate != nil { + mCertificate := map[string]interface{}{} + + if acm := certificate.Acm; acm != nil { + mAcm := map[string]interface{}{ + "certificate_arn": aws.StringValue(acm.CertificateArn), + } + + mCertificate["acm"] = []interface{}{mAcm} } - mCertificate["acm"] = []interface{}{mAcm} - } + if file := certificate.File; file != nil { + mFile := map[string]interface{}{ + "certificate_chain": aws.StringValue(file.CertificateChain), + "private_key": aws.StringValue(file.PrivateKey), + } - if file := certificate.File; file != nil { - mFile := map[string]interface{}{ - "certificate_chain": aws.StringValue(file.CertificateChain), - "private_key": aws.StringValue(file.PrivateKey), + mCertificate["file"] = []interface{}{mFile} } - mCertificate["file"] = []interface{}{mFile} - } + if sds := certificate.Sds; sds != nil { + mSds := map[string]interface{}{ + "secret_name": aws.StringValue(sds.SecretName), + } - if sds := certificate.Sds; sds != nil { - mSds := map[string]interface{}{ - "secret_name": aws.StringValue(sds.SecretName), + mCertificate["sds"] = []interface{}{mSds} } - mCertificate["sds"] = []interface{}{mSds} + mTls["certificate"] = []interface{}{mCertificate} } - mTls["certificate"] = []interface{}{mCertificate} - } + if validation := tls.Validation; validation != nil { + mValidation := map[string]interface{}{} - if validation := tls.Validation; validation != nil { - mValidation := map[string]interface{}{} + if subjectAlternativeNames := validation.SubjectAlternativeNames; subjectAlternativeNames != nil { + mSubjectAlternativeNames := map[string]interface{}{} - if subjectAlternativeNames := validation.SubjectAlternativeNames; subjectAlternativeNames != nil { - mSubjectAlternativeNames := map[string]interface{}{} + if match := subjectAlternativeNames.Match; match != nil { + mMatch := map[string]interface{}{ + "exact": flex.FlattenStringSet(match.Exact), + } - if match := subjectAlternativeNames.Match; match != nil { - mMatch := map[string]interface{}{ - "exact": flex.FlattenStringSet(match.Exact), + mSubjectAlternativeNames["match"] = []interface{}{mMatch} } - mSubjectAlternativeNames["match"] = []interface{}{mMatch} + mValidation["subject_alternative_names"] = []interface{}{mSubjectAlternativeNames} } - mValidation["subject_alternative_names"] = []interface{}{mSubjectAlternativeNames} - } + if trust := validation.Trust; trust != nil { + mTrust := map[string]interface{}{} - if trust := validation.Trust; trust != nil { - mTrust := map[string]interface{}{} + if file := trust.File; file != nil { + mFile := map[string]interface{}{ + "certificate_chain": aws.StringValue(file.CertificateChain), + } - if file := trust.File; file != nil { - mFile := map[string]interface{}{ - "certificate_chain": aws.StringValue(file.CertificateChain), + mTrust["file"] = []interface{}{mFile} } - mTrust["file"] = []interface{}{mFile} - } + if sds := trust.Sds; sds != nil { + mSds := map[string]interface{}{ + "secret_name": aws.StringValue(sds.SecretName), + } - if sds := trust.Sds; sds != nil { - mSds := map[string]interface{}{ - "secret_name": aws.StringValue(sds.SecretName), + mTrust["sds"] = []interface{}{mSds} } - mTrust["sds"] = []interface{}{mSds} + mValidation["trust"] = []interface{}{mTrust} } - mValidation["trust"] = []interface{}{mTrust} + mTls["validation"] = []interface{}{mValidation} } - mTls["validation"] = []interface{}{mValidation} + mListener["tls"] = []interface{}{mTls} } - mListener["tls"] = []interface{}{mTls} + vListeners = append(vListeners, mListener) } - mSpec["listener"] = []interface{}{mListener} + mSpec["listener"] = vListeners } if logging := spec.Logging; logging != nil { @@ -1725,18 +1741,23 @@ func flattenVirtualRouterSpec(spec *appmesh.VirtualRouterSpec) []interface{} { return []interface{}{} } mSpec := make(map[string]interface{}) - if spec.Listeners != nil && spec.Listeners[0] != nil { - // Per schema definition, set at most 1 Listener - listener := spec.Listeners[0] - mListener := make(map[string]interface{}) - if listener.PortMapping != nil { - mPortMapping := map[string]interface{}{ - "port": int(aws.Int64Value(listener.PortMapping.Port)), - "protocol": aws.StringValue(listener.PortMapping.Protocol), + + if listeners := spec.Listeners; listeners != nil && spec.Listeners[0] != nil { + vListeners := []interface{}{} + + for _, listener := range listeners { + mListener := make(map[string]interface{}) + if listener.PortMapping != nil { + mPortMapping := map[string]interface{}{ + "port": int(aws.Int64Value(listener.PortMapping.Port)), + "protocol": aws.StringValue(listener.PortMapping.Protocol), + } + mListener["port_mapping"] = []interface{}{mPortMapping} } - mListener["port_mapping"] = []interface{}{mPortMapping} + vListeners = append(vListeners, mListener) } - mSpec["listener"] = []interface{}{mListener} + + mSpec["listener"] = vListeners } return []interface{}{mSpec} diff --git a/internal/service/appmesh/gateway_route.go b/internal/service/appmesh/gateway_route.go index 15b8c3e4453..d26b1eeda10 100644 --- a/internal/service/appmesh/gateway_route.go +++ b/internal/service/appmesh/gateway_route.go @@ -102,6 +102,11 @@ func ResourceGatewayRoute() *schema.Resource { }, }, }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, }, }, }, @@ -168,6 +173,11 @@ func ResourceGatewayRoute() *schema.Resource { }, }, }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, }, }, }, @@ -330,6 +340,11 @@ func ResourceGatewayRoute() *schema.Resource { }, }, }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, }, }, }, @@ -733,6 +748,10 @@ func expandGatewayRouteTarget(vRouteTarget []interface{}) *appmesh.GatewayRouteT routeTarget.VirtualService = virtualService } + if vPort, ok := mRouteTarget["port"].(int); ok && vPort > 0 { + routeTarget.Port = aws.Int64(int64(vPort)) + } + return routeTarget } @@ -883,7 +902,9 @@ func flattenGatewayRouteTarget(routeTarget *appmesh.GatewayRouteTarget) []interf return []interface{}{} } - mRouteTarget := map[string]interface{}{} + mRouteTarget := map[string]interface{}{ + "port": int(aws.Int64Value(routeTarget.Port)), + } if virtualService := routeTarget.VirtualService; virtualService != nil { mVirtualService := map[string]interface{}{ diff --git a/internal/service/appmesh/gateway_route_test.go b/internal/service/appmesh/gateway_route_test.go index 86e77ae7bc5..b89a38a4d30 100644 --- a/internal/service/appmesh/gateway_route_test.go +++ b/internal/service/appmesh/gateway_route_test.go @@ -92,6 +92,7 @@ func testAccGatewayRoute_GRPCRoute(t *testing.T) { resourceName := "aws_appmesh_gateway_route.test" vs1ResourceName := "aws_appmesh_virtual_service.test.0" vs2ResourceName := "aws_appmesh_virtual_service.test.1" + vsMultiResourceName := "aws_appmesh_virtual_service.multi_test" meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) grName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -150,6 +151,31 @@ func testAccGatewayRoute_GRPCRoute(t *testing.T) { acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), ), }, + { + Config: testAccGatewayRouteConfig_grpcRouteTargetPort(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", grName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.target.0.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.grpc_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vsMultiResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", "multi-test"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, { ResourceName: resourceName, ImportStateIdFunc: testAccGatewayRouteImportStateIdFunc(resourceName), @@ -165,6 +191,7 @@ func testAccGatewayRoute_HTTPRoute(t *testing.T) { resourceName := "aws_appmesh_gateway_route.test" vs1ResourceName := "aws_appmesh_virtual_service.test.0" vs2ResourceName := "aws_appmesh_virtual_service.test.1" + vsMultiResourceName := "aws_appmesh_virtual_service.multi_test" meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) grName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -277,6 +304,31 @@ func testAccGatewayRoute_HTTPRoute(t *testing.T) { acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), ), }, + { + Config: testAccGatewayRouteConfig_httpRouteTargetPort(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", grName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.target.0.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vsMultiResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/"), + resource.TestCheckResourceAttr(resourceName, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, { ResourceName: resourceName, ImportStateIdFunc: testAccGatewayRouteImportStateIdFunc(resourceName), @@ -292,6 +344,7 @@ func testAccGatewayRoute_HTTP2Route(t *testing.T) { resourceName := "aws_appmesh_gateway_route.test" vs1ResourceName := "aws_appmesh_virtual_service.test.0" vs2ResourceName := "aws_appmesh_virtual_service.test.1" + vsMultiResourceName := "aws_appmesh_virtual_service.multi_test" meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) grName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -404,6 +457,31 @@ func testAccGatewayRoute_HTTP2Route(t *testing.T) { acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), ), }, + { + Config: testAccGatewayRouteConfig_http2RouteTargetPort(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", grName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.target.0.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http2_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vsMultiResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.prefix", "/"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, { ResourceName: resourceName, ImportStateIdFunc: testAccGatewayRouteImportStateIdFunc(resourceName), @@ -549,6 +627,50 @@ resource "aws_appmesh_virtual_service" "test" { `, meshName, vgName) } +func testAccGatewayRouteConfig_ServiceNodeMultipleListeners(vnName string, protocol string) string { + return fmt.Sprintf(` +resource "aws_appmesh_virtual_node" "test" { + name = "%[1]s-multi-listener-vn" + mesh_name = aws_appmesh_mesh.test.name + + spec { + listener { + port_mapping { + port = 8080 + protocol = "%[2]s" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "%[2]s" + } + } + + service_discovery { + dns { + hostname = "serviceb.simpleapp.local" + } + } + } +} + +resource "aws_appmesh_virtual_service" "multi_test" { + name = "%[1]s-multi-listener" + mesh_name = aws_appmesh_mesh.test.name + + spec { + provider { + virtual_node { + virtual_node_name = aws_appmesh_virtual_node.test.name + } + } + } +} +`, vnName, protocol) +} + func testAccGatewayRouteConfig_grpcRoute(meshName, vgName, grName string) string { return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { @@ -601,6 +723,36 @@ resource "aws_appmesh_gateway_route" "test" { `, grName)) } +func testAccGatewayRouteConfig_grpcRouteTargetPort(meshName, vgName, grName string) string { + return acctest.ConfigCompose( + testAccGatewayRouteConfigBase(meshName, vgName), + testAccGatewayRouteConfig_ServiceNodeMultipleListeners(vgName, "grpc"), + fmt.Sprintf(` +resource "aws_appmesh_gateway_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.name + virtual_gateway_name = aws_appmesh_virtual_gateway.test.name + + spec { + grpc_route { + action { + target { + virtual_service { + virtual_service_name = aws_appmesh_virtual_service.multi_test.name + } + port = 8080 + } + } + + match { + service_name = "multi-test" + } + } + } +} +`, grName)) +} + func testAccGatewayRouteConfig_httpRoute(meshName, vgName, grName string) string { return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { @@ -715,6 +867,36 @@ resource "aws_appmesh_gateway_route" "test" { `, grName)) } +func testAccGatewayRouteConfig_httpRouteTargetPort(meshName, vgName, grName string) string { + return acctest.ConfigCompose( + testAccGatewayRouteConfigBase(meshName, vgName), + testAccGatewayRouteConfig_ServiceNodeMultipleListeners(vgName, "http"), + fmt.Sprintf(` +resource "aws_appmesh_gateway_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.name + virtual_gateway_name = aws_appmesh_virtual_gateway.test.name + + spec { + http_route { + action { + target { + virtual_service { + virtual_service_name = aws_appmesh_virtual_service.multi_test.name + } + port = 8080 + } + } + + match { + prefix = "/" + } + } + } +} +`, grName)) +} + func testAccGatewayRouteConfig_http2Route(meshName, vgName, grName string) string { return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { @@ -829,6 +1011,36 @@ resource "aws_appmesh_gateway_route" "test" { `, grName)) } +func testAccGatewayRouteConfig_http2RouteTargetPort(meshName, vgName, grName string) string { + return acctest.ConfigCompose( + testAccGatewayRouteConfigBase(meshName, vgName), + testAccGatewayRouteConfig_ServiceNodeMultipleListeners(vgName, "http2"), + fmt.Sprintf(` +resource "aws_appmesh_gateway_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.name + virtual_gateway_name = aws_appmesh_virtual_gateway.test.name + + spec { + http2_route { + action { + target { + virtual_service { + virtual_service_name = aws_appmesh_virtual_service.multi_test.name + } + port = 8080 + } + } + + match { + prefix = "/" + } + } + } +} +`, grName)) +} + func testAccGatewayRouteConfig_tags1(meshName, vgName, grName, tagKey1, tagValue1 string) string { return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { diff --git a/internal/service/appmesh/route.go b/internal/service/appmesh/route.go index b5edf1e7160..a9a30aeb4d2 100644 --- a/internal/service/appmesh/route.go +++ b/internal/service/appmesh/route.go @@ -99,6 +99,11 @@ func ResourceRoute() *schema.Resource { Required: true, ValidateFunc: validation.IntBetween(0, 100), }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, }, }, }, @@ -375,6 +380,11 @@ func ResourceRoute() *schema.Resource { Required: true, ValidateFunc: validation.IntBetween(0, 100), }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, }, }, }, @@ -482,6 +492,11 @@ func RouteHTTPRouteSchema() *schema.Schema { Required: true, ValidateFunc: validation.IntBetween(0, 100), }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumber, + }, }, }, }, diff --git a/internal/service/appmesh/route_test.go b/internal/service/appmesh/route_test.go index 53ed0797897..6302a0672cb 100644 --- a/internal/service/appmesh/route_test.go +++ b/internal/service/appmesh/route_test.go @@ -381,6 +381,54 @@ func testAccRoute_grpcRouteEmptyMatch(t *testing.T) { }) } +func testAccRoute_grpcRouteTargetPort(t *testing.T) { + var r appmesh.RouteData + resourceName := "aws_appmesh_route.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn1Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn2Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRouteConfig_grpcRouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteExists(resourceName, &r), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "virtual_router_name", vrName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualRouter/%s/route/%s", meshName, vrName, rName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccRoute_http2Route(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -601,6 +649,54 @@ func testAccRoute_http2RouteTimeout(t *testing.T) { }) } +func testAccRoute_http2RouteTargetPort(t *testing.T) { + var r appmesh.RouteData + resourceName := "aws_appmesh_route.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn1Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn2Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRouteConfig_http2RouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteExists(resourceName, &r), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "virtual_router_name", vrName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualRouter/%s/route/%s", meshName, vrName, rName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccRoute_httpRoute(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -807,6 +903,54 @@ func testAccRoute_httpRouteTimeout(t *testing.T) { }) } +func testAccRoute_httpRouteTargetPort(t *testing.T) { + var r appmesh.RouteData + resourceName := "aws_appmesh_route.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn1Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn2Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRouteConfig_httpRouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteExists(resourceName, &r), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "virtual_router_name", vrName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualRouter/%s/route/%s", meshName, vrName, rName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccRoute_tcpRoute(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -980,6 +1124,54 @@ func testAccRoute_tcpRouteTimeout(t *testing.T) { }) } +func testAccRoute_tcpRouteTargetPort(t *testing.T) { + var r appmesh.RouteData + resourceName := "aws_appmesh_route.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn1Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vn2Name := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRouteConfig_tcpRouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteExists(resourceName, &r), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "virtual_router_name", vrName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.action.0.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualRouter/%s/route/%s", meshName, vrName, rName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccRoute_tags(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -1443,6 +1635,37 @@ resource "aws_appmesh_virtual_node" "bar" { `, meshName, vrName, vrProtocol, vn1Name, vn2Name) } +func testAccRouteConfig_NodeMultipleListeners(vnName string, vnProtocol string) string { + return fmt.Sprintf(` +resource "aws_appmesh_virtual_node" "multi" { + name = "%[1]s-multi-listener-vn" + mesh_name = aws_appmesh_mesh.test.name + + spec { + listener { + port_mapping { + port = 8080 + protocol = "%[2]s" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "%[2]s" + } + } + + service_discovery { + dns { + hostname = "serviceb.simpleapp.local" + } + } + } +} +`, vnName, vnProtocol) +} + func testAccRouteConfig_grpcRoute(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "grpc", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { @@ -1769,6 +1992,33 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_grpcRouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose( + testAccRouteConfigBase(meshName, vrName, "grpc", vn1Name, vn2Name), + testAccRouteConfig_NodeMultipleListeners(meshName, "grpc"), + fmt.Sprintf(` +resource "aws_appmesh_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + virtual_router_name = aws_appmesh_virtual_router.test.name + + spec { + grpc_route { + match {} + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.multi.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + func testAccRouteConfig_http2Route(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http2", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { @@ -1956,6 +2206,41 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_http2RouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose( + testAccRouteConfigBase(meshName, vrName, "http2", vn1Name, vn2Name), + testAccRouteConfig_NodeMultipleListeners(meshName, "http2"), + fmt.Sprintf(` +resource "aws_appmesh_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + virtual_router_name = aws_appmesh_virtual_router.test.name + + spec { + http2_route { + match { + prefix = "/" + method = "POST" + scheme = "http" + + header { + name = "X-Testing1" + } + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.multi.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + func testAccRouteConfig_http(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { @@ -2110,6 +2395,35 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_httpRouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose( + testAccRouteConfigBase(meshName, vrName, "http", vn1Name, vn2Name), + testAccRouteConfig_NodeMultipleListeners(meshName, "http"), + fmt.Sprintf(` +resource "aws_appmesh_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + virtual_router_name = aws_appmesh_virtual_router.test.name + + spec { + http_route { + match { + prefix = "/" + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.multi.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + func testAccRouteConfig_tcp(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "tcp", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { @@ -2239,6 +2553,31 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_tcpRouteTargetPort(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose( + testAccRouteConfigBase(meshName, vrName, "tcp", vn1Name, vn2Name), + testAccRouteConfig_NodeMultipleListeners(meshName, "tcp"), + fmt.Sprintf(` +resource "aws_appmesh_route" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + virtual_router_name = aws_appmesh_virtual_router.test.name + + spec { + tcp_route { + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.multi.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + func testAccRouteConfig_tags(meshName, vrName, vn1Name, vn2Name, rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { diff --git a/internal/service/appmesh/virtual_gateway.go b/internal/service/appmesh/virtual_gateway.go index e918dcf9707..ce180189e33 100644 --- a/internal/service/appmesh/virtual_gateway.go +++ b/internal/service/appmesh/virtual_gateway.go @@ -278,7 +278,7 @@ func ResourceVirtualGateway() *schema.Resource { Type: schema.TypeList, Required: true, MinItems: 1, - MaxItems: 1, + MaxItems: 50, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "connection_pool": { @@ -302,11 +302,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - }, }, "http": { @@ -329,11 +324,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - }, }, "http2": { @@ -350,11 +340,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - }, }, }, }, @@ -462,11 +447,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.certificate.0.acm", - "spec.0.listener.0.tls.0.certificate.0.file", - "spec.0.listener.0.tls.0.certificate.0.sds", - }, }, "file": { @@ -489,11 +469,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.certificate.0.acm", - "spec.0.listener.0.tls.0.certificate.0.file", - "spec.0.listener.0.tls.0.certificate.0.sds", - }, }, "sds": { @@ -509,11 +484,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.certificate.0.acm", - "spec.0.listener.0.tls.0.certificate.0.file", - "spec.0.listener.0.tls.0.certificate.0.sds", - }, }, }, }, @@ -580,10 +550,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.validation.0.trust.0.file", - "spec.0.listener.0.tls.0.validation.0.trust.0.sds", - }, }, "sds": { @@ -600,10 +566,6 @@ func ResourceVirtualGateway() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.validation.0.trust.0.file", - "spec.0.listener.0.tls.0.validation.0.trust.0.sds", - }, }, }, }, @@ -1308,142 +1270,146 @@ func flattenVirtualGatewaySpec(spec *appmesh.VirtualGatewaySpec) []interface{} { mSpec["backend_defaults"] = []interface{}{mBackendDefaults} } - if spec.Listeners != nil && spec.Listeners[0] != nil { - // Per schema definition, set at most 1 Listener - listener := spec.Listeners[0] - mListener := map[string]interface{}{} + if listeners := spec.Listeners; listeners != nil && spec.Listeners[0] != nil { + vListeners := []interface{}{} - if connectionPool := listener.ConnectionPool; connectionPool != nil { - mConnectionPool := map[string]interface{}{} + for _, listener := range listeners { + mListener := map[string]interface{}{} - if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { - mGrpcConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), - } - mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} - } + if connectionPool := listener.ConnectionPool; connectionPool != nil { + mConnectionPool := map[string]interface{}{} - if httpConnectionPool := connectionPool.Http; httpConnectionPool != nil { - mHttpConnectionPool := map[string]interface{}{ - "max_connections": int(aws.Int64Value(httpConnectionPool.MaxConnections)), - "max_pending_requests": int(aws.Int64Value(httpConnectionPool.MaxPendingRequests)), + if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { + mGrpcConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), + } + mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} } - mConnectionPool["http"] = []interface{}{mHttpConnectionPool} - } - if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { - mHttp2ConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + if httpConnectionPool := connectionPool.Http; httpConnectionPool != nil { + mHttpConnectionPool := map[string]interface{}{ + "max_connections": int(aws.Int64Value(httpConnectionPool.MaxConnections)), + "max_pending_requests": int(aws.Int64Value(httpConnectionPool.MaxPendingRequests)), + } + mConnectionPool["http"] = []interface{}{mHttpConnectionPool} } - mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} - } - mListener["connection_pool"] = []interface{}{mConnectionPool} - } + if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { + mHttp2ConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + } + mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} + } - if healthCheck := listener.HealthCheck; healthCheck != nil { - mHealthCheck := map[string]interface{}{ - "healthy_threshold": int(aws.Int64Value(healthCheck.HealthyThreshold)), - "interval_millis": int(aws.Int64Value(healthCheck.IntervalMillis)), - "path": aws.StringValue(healthCheck.Path), - "port": int(aws.Int64Value(healthCheck.Port)), - "protocol": aws.StringValue(healthCheck.Protocol), - "timeout_millis": int(aws.Int64Value(healthCheck.TimeoutMillis)), - "unhealthy_threshold": int(aws.Int64Value(healthCheck.UnhealthyThreshold)), + mListener["connection_pool"] = []interface{}{mConnectionPool} } - mListener["health_check"] = []interface{}{mHealthCheck} - } - if portMapping := listener.PortMapping; portMapping != nil { - mPortMapping := map[string]interface{}{ - "port": int(aws.Int64Value(portMapping.Port)), - "protocol": aws.StringValue(portMapping.Protocol), + if healthCheck := listener.HealthCheck; healthCheck != nil { + mHealthCheck := map[string]interface{}{ + "healthy_threshold": int(aws.Int64Value(healthCheck.HealthyThreshold)), + "interval_millis": int(aws.Int64Value(healthCheck.IntervalMillis)), + "path": aws.StringValue(healthCheck.Path), + "port": int(aws.Int64Value(healthCheck.Port)), + "protocol": aws.StringValue(healthCheck.Protocol), + "timeout_millis": int(aws.Int64Value(healthCheck.TimeoutMillis)), + "unhealthy_threshold": int(aws.Int64Value(healthCheck.UnhealthyThreshold)), + } + mListener["health_check"] = []interface{}{mHealthCheck} } - mListener["port_mapping"] = []interface{}{mPortMapping} - } - if tls := listener.Tls; tls != nil { - mTls := map[string]interface{}{ - "mode": aws.StringValue(tls.Mode), + if portMapping := listener.PortMapping; portMapping != nil { + mPortMapping := map[string]interface{}{ + "port": int(aws.Int64Value(portMapping.Port)), + "protocol": aws.StringValue(portMapping.Protocol), + } + mListener["port_mapping"] = []interface{}{mPortMapping} } - if certificate := tls.Certificate; certificate != nil { - mCertificate := map[string]interface{}{} + if tls := listener.Tls; tls != nil { + mTls := map[string]interface{}{ + "mode": aws.StringValue(tls.Mode), + } - if acm := certificate.Acm; acm != nil { - mAcm := map[string]interface{}{ - "certificate_arn": aws.StringValue(acm.CertificateArn), + if certificate := tls.Certificate; certificate != nil { + mCertificate := map[string]interface{}{} + + if acm := certificate.Acm; acm != nil { + mAcm := map[string]interface{}{ + "certificate_arn": aws.StringValue(acm.CertificateArn), + } + + mCertificate["acm"] = []interface{}{mAcm} } - mCertificate["acm"] = []interface{}{mAcm} - } + if file := certificate.File; file != nil { + mFile := map[string]interface{}{ + "certificate_chain": aws.StringValue(file.CertificateChain), + "private_key": aws.StringValue(file.PrivateKey), + } - if file := certificate.File; file != nil { - mFile := map[string]interface{}{ - "certificate_chain": aws.StringValue(file.CertificateChain), - "private_key": aws.StringValue(file.PrivateKey), + mCertificate["file"] = []interface{}{mFile} } - mCertificate["file"] = []interface{}{mFile} - } + if sds := certificate.Sds; sds != nil { + mSds := map[string]interface{}{ + "secret_name": aws.StringValue(sds.SecretName), + } - if sds := certificate.Sds; sds != nil { - mSds := map[string]interface{}{ - "secret_name": aws.StringValue(sds.SecretName), + mCertificate["sds"] = []interface{}{mSds} } - mCertificate["sds"] = []interface{}{mSds} + mTls["certificate"] = []interface{}{mCertificate} } - mTls["certificate"] = []interface{}{mCertificate} - } + if validation := tls.Validation; validation != nil { + mValidation := map[string]interface{}{} - if validation := tls.Validation; validation != nil { - mValidation := map[string]interface{}{} + if subjectAlternativeNames := validation.SubjectAlternativeNames; subjectAlternativeNames != nil { + mSubjectAlternativeNames := map[string]interface{}{} - if subjectAlternativeNames := validation.SubjectAlternativeNames; subjectAlternativeNames != nil { - mSubjectAlternativeNames := map[string]interface{}{} + if match := subjectAlternativeNames.Match; match != nil { + mMatch := map[string]interface{}{ + "exact": flex.FlattenStringSet(match.Exact), + } - if match := subjectAlternativeNames.Match; match != nil { - mMatch := map[string]interface{}{ - "exact": flex.FlattenStringSet(match.Exact), + mSubjectAlternativeNames["match"] = []interface{}{mMatch} } - mSubjectAlternativeNames["match"] = []interface{}{mMatch} + mValidation["subject_alternative_names"] = []interface{}{mSubjectAlternativeNames} } - mValidation["subject_alternative_names"] = []interface{}{mSubjectAlternativeNames} - } + if trust := validation.Trust; trust != nil { + mTrust := map[string]interface{}{} - if trust := validation.Trust; trust != nil { - mTrust := map[string]interface{}{} + if file := trust.File; file != nil { + mFile := map[string]interface{}{ + "certificate_chain": aws.StringValue(file.CertificateChain), + } - if file := trust.File; file != nil { - mFile := map[string]interface{}{ - "certificate_chain": aws.StringValue(file.CertificateChain), + mTrust["file"] = []interface{}{mFile} } - mTrust["file"] = []interface{}{mFile} - } + if sds := trust.Sds; sds != nil { + mSds := map[string]interface{}{ + "secret_name": aws.StringValue(sds.SecretName), + } - if sds := trust.Sds; sds != nil { - mSds := map[string]interface{}{ - "secret_name": aws.StringValue(sds.SecretName), + mTrust["sds"] = []interface{}{mSds} } - mTrust["sds"] = []interface{}{mSds} + mValidation["trust"] = []interface{}{mTrust} } - mValidation["trust"] = []interface{}{mTrust} + mTls["validation"] = []interface{}{mValidation} } - mTls["validation"] = []interface{}{mValidation} + mListener["tls"] = []interface{}{mTls} } - mListener["tls"] = []interface{}{mTls} + vListeners = append(vListeners, mListener) } - mSpec["listener"] = []interface{}{mListener} + mSpec["listener"] = vListeners } if logging := spec.Logging; logging != nil { diff --git a/internal/service/appmesh/virtual_gateway_test.go b/internal/service/appmesh/virtual_gateway_test.go index 9c93e206d59..0693d7d36a4 100644 --- a/internal/service/appmesh/virtual_gateway_test.go +++ b/internal/service/appmesh/virtual_gateway_test.go @@ -607,6 +607,92 @@ func testAccVirtualGateway_ListenerValidation(t *testing.T) { }) } +func testAccVirtualGateway_multipleListeners(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVirtualGatewayConfig_multipleListeners(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", vgName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s", meshName, vgName)), + ), + }, + { + Config: testAccVirtualGatewayConfig_multipleListenersUpdated(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", vgName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.port", "9000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccVirtualGateway_Logging(t *testing.T) { var v appmesh.VirtualGatewayData resourceName := "aws_appmesh_virtual_gateway.test" @@ -1295,3 +1381,68 @@ resource "aws_appmesh_virtual_gateway" "test" { } `, meshName, vgName, tagKey1, tagValue1, tagKey2, tagValue2) } + +func testAccVirtualGatewayConfig_multipleListeners(meshName, vgName string) string { + return fmt.Sprintf(` +resource "aws_appmesh_mesh" "test" { + name = %[1]q +} + +resource "aws_appmesh_virtual_gateway" "test" { + name = %[2]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + } + } +} +`, meshName, vgName) +} + +func testAccVirtualGatewayConfig_multipleListenersUpdated(meshName, vgName string) string { + return fmt.Sprintf(` +resource "aws_appmesh_mesh" "test" { + name = %[1]q +} + +resource "aws_appmesh_virtual_gateway" "test" { + name = %[2]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + } + + listener { + port_mapping { + port = 9000 + protocol = "http" + } + } + } +} +`, meshName, vgName) +} diff --git a/internal/service/appmesh/virtual_node.go b/internal/service/appmesh/virtual_node.go index b7aec5853f0..f6cdf52e8c5 100644 --- a/internal/service/appmesh/virtual_node.go +++ b/internal/service/appmesh/virtual_node.go @@ -105,7 +105,7 @@ func ResourceVirtualNode() *schema.Resource { Type: schema.TypeList, Optional: true, MinItems: 0, - MaxItems: 1, + MaxItems: 50, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "connection_pool": { @@ -129,12 +129,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - "spec.0.listener.0.connection_pool.0.tcp", - }, }, "http": { @@ -157,12 +151,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - "spec.0.listener.0.connection_pool.0.tcp", - }, }, "http2": { @@ -179,12 +167,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - "spec.0.listener.0.connection_pool.0.tcp", - }, }, "tcp": { @@ -201,12 +183,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.connection_pool.0.grpc", - "spec.0.listener.0.connection_pool.0.http", - "spec.0.listener.0.connection_pool.0.http2", - "spec.0.listener.0.connection_pool.0.tcp", - }, }, }, }, @@ -407,12 +383,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.timeout.0.grpc", - "spec.0.listener.0.timeout.0.http", - "spec.0.listener.0.timeout.0.http2", - "spec.0.listener.0.timeout.0.tcp", - }, }, "http": { @@ -465,12 +435,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.timeout.0.grpc", - "spec.0.listener.0.timeout.0.http", - "spec.0.listener.0.timeout.0.http2", - "spec.0.listener.0.timeout.0.tcp", - }, }, "http2": { @@ -523,12 +487,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.timeout.0.grpc", - "spec.0.listener.0.timeout.0.http", - "spec.0.listener.0.timeout.0.http2", - "spec.0.listener.0.timeout.0.tcp", - }, }, "tcp": { @@ -560,12 +518,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.timeout.0.grpc", - "spec.0.listener.0.timeout.0.http", - "spec.0.listener.0.timeout.0.http2", - "spec.0.listener.0.timeout.0.tcp", - }, }, }, }, @@ -599,11 +551,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.certificate.0.acm", - "spec.0.listener.0.tls.0.certificate.0.file", - "spec.0.listener.0.tls.0.certificate.0.sds", - }, }, "file": { @@ -626,11 +573,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.certificate.0.acm", - "spec.0.listener.0.tls.0.certificate.0.file", - "spec.0.listener.0.tls.0.certificate.0.sds", - }, }, "sds": { @@ -646,11 +588,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.certificate.0.acm", - "spec.0.listener.0.tls.0.certificate.0.file", - "spec.0.listener.0.tls.0.certificate.0.sds", - }, }, }, }, @@ -717,10 +654,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.validation.0.trust.0.file", - "spec.0.listener.0.tls.0.validation.0.trust.0.sds", - }, }, "sds": { @@ -737,10 +670,6 @@ func ResourceVirtualNode() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "spec.0.listener.0.tls.0.validation.0.trust.0.file", - "spec.0.listener.0.tls.0.validation.0.trust.0.sds", - }, }, }, }, diff --git a/internal/service/appmesh/virtual_node_test.go b/internal/service/appmesh/virtual_node_test.go index de82254db2f..63183bc31b0 100644 --- a/internal/service/appmesh/virtual_node_test.go +++ b/internal/service/appmesh/virtual_node_test.go @@ -1119,6 +1119,110 @@ func testAccVirtualNode_listenerValidation(t *testing.T) { }) } +func testAccVirtualNode_multipleListeners(t *testing.T) { + var vn appmesh.VirtualNodeData + resourceName := "aws_appmesh_virtual_node.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vnName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVirtualNodeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVirtualNodeConfig_multipleListeners(meshName, vnName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVirtualNodeExists(resourceName, &vn), + resource.TestCheckResourceAttr(resourceName, "name", vnName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.backend.*", map[string]string{ + "virtual_service.#": "1", + "virtual_service.0.client_policy.#": "0", + "virtual_service.0.virtual_service_name": "servicea.simpleapp.local", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.outlier_detection.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.outlier_detection.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.service_discovery.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.service_discovery.0.dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.service_discovery.0.dns.0.hostname", "serviceb.simpleapp.local"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualNode/%s", meshName, vnName)), + ), + }, + { + Config: testAccVirtualNodeConfig_multipleListenersUpdated(meshName, vnName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVirtualNodeExists(resourceName, &vn), + resource.TestCheckResourceAttr(resourceName, "name", vnName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.backend.*", map[string]string{ + "virtual_service.#": "1", + "virtual_service.0.client_policy.#": "0", + "virtual_service.0.virtual_service_name": "servicea.simpleapp.local", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.outlier_detection.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.outlier_detection.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.connection_pool.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.outlier_detection.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.port", "9000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.service_discovery.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.service_discovery.0.dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.service_discovery.0.dns.0.hostname", "serviceb.simpleapp.local"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualNode/%s", meshName, vnName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vnName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccVirtualNode_logging(t *testing.T) { var vn appmesh.VirtualNodeData resourceName := "aws_appmesh_virtual_node.test" @@ -2156,3 +2260,84 @@ resource "aws_appmesh_virtual_node" "test" { } `, vnName, tagKey1, tagValue1, tagKey2, tagValue2)) } + +func testAccVirtualNodeConfig_multipleListeners(meshName, vnName string) string { + return acctest.ConfigCompose(testAccVirtualNodeConfig_mesh(meshName), fmt.Sprintf(` +resource "aws_appmesh_virtual_node" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + backend { + virtual_service { + virtual_service_name = "servicea.simpleapp.local" + } + } + + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + } + + service_discovery { + dns { + hostname = "serviceb.simpleapp.local" + } + } + } +} +`, vnName)) +} + +func testAccVirtualNodeConfig_multipleListenersUpdated(meshName, vnName string) string { + return acctest.ConfigCompose(testAccVirtualNodeConfig_mesh(meshName), fmt.Sprintf(` +resource "aws_appmesh_virtual_node" "test" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + backend { + virtual_service { + virtual_service_name = "servicea.simpleapp.local" + } + } + + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + } + + listener { + port_mapping { + port = 9000 + protocol = "http" + } + } + + service_discovery { + dns { + hostname = "serviceb.simpleapp.local" + } + } + } +} +`, vnName)) +} diff --git a/internal/service/appmesh/virtual_router.go b/internal/service/appmesh/virtual_router.go index 477e0b407a6..422094315a4 100644 --- a/internal/service/appmesh/virtual_router.go +++ b/internal/service/appmesh/virtual_router.go @@ -66,7 +66,7 @@ func ResourceVirtualRouter() *schema.Resource { Type: schema.TypeList, Required: true, MinItems: 1, - MaxItems: 1, + MaxItems: 50, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "port_mapping": { diff --git a/internal/service/appmesh/virtual_router_test.go b/internal/service/appmesh/virtual_router_test.go index 36ca66bb491..65984531134 100644 --- a/internal/service/appmesh/virtual_router_test.go +++ b/internal/service/appmesh/virtual_router_test.go @@ -68,6 +68,69 @@ func testAccVirtualRouter_basic(t *testing.T) { }) } +func testAccVirtualRouter_multipleListeners(t *testing.T) { + var vr appmesh.VirtualRouterData + resourceName := "aws_appmesh_virtual_router.test" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appmesh.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appmesh.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckVirtualRouterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVirtualRouterConfig_multipleListeners(meshName, vrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVirtualRouterExists(resourceName, &vr), + resource.TestCheckResourceAttr(resourceName, "name", vrName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualRouter/%s", meshName, vrName)), + ), + }, + { + Config: testAccVirtualRouterConfig_multipleListenersUpdated(meshName, vrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVirtualRouterExists(resourceName, &vr), + resource.TestCheckResourceAttr(resourceName, "name", vrName), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + acctest.CheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.port", "9000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.protocol", "http"), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vrName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccVirtualRouter_tags(t *testing.T) { var vr appmesh.VirtualRouterData resourceName := "aws_appmesh_virtual_router.test" @@ -219,6 +282,71 @@ resource "aws_appmesh_virtual_router" "test" { `, meshName, vrName) } +func testAccVirtualRouterConfig_multipleListeners(meshName, vrName string) string { + return fmt.Sprintf(` +resource "aws_appmesh_mesh" "test" { + name = %[1]q +} + +resource "aws_appmesh_virtual_router" "test" { + name = %[2]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + } + } +} +`, meshName, vrName) +} + +func testAccVirtualRouterConfig_multipleListenersUpdated(meshName, vrName string) string { + return fmt.Sprintf(` +resource "aws_appmesh_mesh" "test" { + name = %[1]q +} + +resource "aws_appmesh_virtual_router" "test" { + name = %[2]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + } + + listener { + port_mapping { + port = 9000 + protocol = "http" + } + } + } +} +`, meshName, vrName) +} + func testAccVirtualRouterConfig_tags(meshName, vrName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_appmesh_mesh" "test" { diff --git a/website/docs/r/appmesh_gateway_route.html.markdown b/website/docs/r/appmesh_gateway_route.html.markdown index c9bb4ef5be7..e7f86cba630 100644 --- a/website/docs/r/appmesh_gateway_route.html.markdown +++ b/website/docs/r/appmesh_gateway_route.html.markdown @@ -69,6 +69,7 @@ The `grpc_route`, `http_route` and `http2_route`'s `action` object supports the The `target` object supports the following: * `virtual_service` - (Required) Virtual service gateway route target. +* `port` - (Optional) The port that will be targeted on the service's backend. Required if the service's Virtual Node or Virtual Router has multiple listeners. The `virtual_service` object supports the following: diff --git a/website/docs/r/appmesh_route.html.markdown b/website/docs/r/appmesh_route.html.markdown index c4c3baf16e2..11baa3b305d 100644 --- a/website/docs/r/appmesh_route.html.markdown +++ b/website/docs/r/appmesh_route.html.markdown @@ -265,6 +265,7 @@ The `weighted_target` object supports the following: * `virtual_node` - (Required) Virtual node to associate with the weighted target. Must be between 1 and 255 characters in length. * `weight` - (Required) Relative weight of the weighted target. An integer between 0 and 100. +* `port` - (Optional) The port that will be targeted on the Virtual Node. Required if the Virtual Node has multiple listeners. The `header` object supports the following: diff --git a/website/docs/r/appmesh_virtual_gateway.html.markdown b/website/docs/r/appmesh_virtual_gateway.html.markdown index 6caa565c3ac..9fb997504bb 100644 --- a/website/docs/r/appmesh_virtual_gateway.html.markdown +++ b/website/docs/r/appmesh_virtual_gateway.html.markdown @@ -82,7 +82,7 @@ The following arguments are supported: The `spec` object supports the following: -* `listener` - (Required) Listeners that the mesh endpoint is expected to receive inbound traffic from. You can specify one listener. +* `listener` - (Required) Listeners that the mesh endpoint is expected to receive inbound traffic from. You can specify up to 50 listeners. * `backend_defaults` - (Optional) Defaults for backends. * `logging` - (Optional) Inbound and outbound access logging information for the virtual gateway. diff --git a/website/docs/r/appmesh_virtual_router.html.markdown b/website/docs/r/appmesh_virtual_router.html.markdown index caaeb7a2666..d7ec24002c2 100644 --- a/website/docs/r/appmesh_virtual_router.html.markdown +++ b/website/docs/r/appmesh_virtual_router.html.markdown @@ -52,8 +52,7 @@ The following arguments are supported: The `spec` object supports the following: -* `listener` - (Required) Listeners that the virtual router is expected to receive inbound traffic from. -Currently only one listener is supported per virtual router. +* `listener` - (Required) Listeners that the virtual router is expected to receive inbound traffic from. Up to 50 listeners may be specified. The `listener` object supports the following: