diff --git a/CHANGELOG.md b/CHANGELOG.md index 633cfb7263c0..0782295f1960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -455,6 +455,11 @@ ENHANCEMENTS: * resource/aws_lightsail_instance: Add `ip_address_type` argument ([#27699](https://github.com/hashicorp/terraform-provider-aws/issues/27699)) * resource/aws_security_group: Do not pass `from_port` or `to_port` values to the AWS API if a `rule`'s `protocol` value is `-1` or `all` ([#27642](https://github.com/hashicorp/terraform-provider-aws/issues/27642)) * resource/aws_wafv2_rule_group: Correct maximum nesting level for `and_statement`, `not_statement`, `or_statement` and `rate_based_statement` ([#27682](https://github.com/hashicorp/terraform-provider-aws/issues/27682)) +* resource/aws_appmesh_virtual_node: Add Support for multiple listeners ([#27698](https://github.com/hashicorp/terraform-provider-aws/issues/27698)) +* resource/aws_appmesh_virtual_router: Add Support for multiple listeners ([#27698](https://github.com/hashicorp/terraform-provider-aws/issues/27698)) +* resource/aws_appmesh_virtual_gateway: Add Support for multiple listeners ([#27698](https://github.com/hashicorp/terraform-provider-aws/issues/27698)) +* resource/aws_appmesh_route: Add Support to match on port and specify weighted target port ([#27698](https://github.com/hashicorp/terraform-provider-aws/issues/27698)) +* resource/aws_appmesh_gateway_route: Add Support to match on port ([#27698](https://github.com/hashicorp/terraform-provider-aws/issues/27698)) BUG FIXES: diff --git a/internal/service/appmesh/appmesh_test.go b/internal/service/appmesh/appmesh_test.go index a7cbf8573c10..5cc5a14a2cfc 100644 --- a/internal/service/appmesh/appmesh_test.go +++ b/internal/service/appmesh/appmesh_test.go @@ -11,12 +11,15 @@ func TestAccAppMesh_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "GatewayRoute": { - "basic": testAccGatewayRoute_basic, - "disappears": testAccGatewayRoute_disappears, - "grpcRoute": testAccGatewayRoute_GRPCRoute, - "httpRoute": testAccGatewayRoute_HTTPRoute, - "http2Route": testAccGatewayRoute_HTTP2Route, - "tags": testAccGatewayRoute_Tags, + "basic": testAccGatewayRoute_basic, + "disappears": testAccGatewayRoute_disappears, + "grpcRoute": testAccGatewayRoute_GRPCRoute, + "grpcRouteWithPort": testAccGatewayRoute_GRPCRouteWithPort, + "httpRoute": testAccGatewayRoute_HTTPRoute, + "httpRouteWithPort": testAccGatewayRoute_HTTPRouteWithPort, + "http2Route": testAccGatewayRoute_HTTP2Route, + "http2RouteWithPort": testAccGatewayRoute_HTTP2RouteWithPort, + "tags": testAccGatewayRoute_Tags, }, "Mesh": { "basic": testAccMesh_basic, @@ -24,19 +27,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, + "grpcRouteWithPortMatch": testAccRoute_grpcRouteWithPortMatch, + "grpcRouteEmptyMatch": testAccRoute_grpcRouteEmptyMatch, + "grpcRouteTimeout": testAccRoute_grpcRouteTimeout, + "http2Route": testAccRoute_http2Route, + "http2RouteWithPortMatch": testAccRoute_http2RouteWithPortMatch, + "http2RouteTimeout": testAccRoute_http2RouteTimeout, + "httpHeader": testAccRoute_httpHeader, + "httpRetryPolicy": testAccRoute_httpRetryPolicy, + "httpRoute": testAccRoute_httpRoute, + "httpRouteWithPortMatch": testAccRoute_httpRouteWithPortMatch, + "httpRouteTimeout": testAccRoute_httpRouteTimeout, + "routePriority": testAccRoute_routePriority, + "tcpRoute": testAccRoute_tcpRoute, + "tcpRouteWithPortMatch": testAccRoute_tcpRouteWithPortMatch, + "tcpRouteTimeout": testAccRoute_tcpRouteTimeout, + "tags": testAccRoute_tags, }, "VirtualGateway": { "basic": testAccVirtualGateway_basic, @@ -47,6 +54,7 @@ func TestAccAppMesh_serial(t *testing.T) { "listenerHealthChecks": testAccVirtualGateway_ListenerHealthChecks, "listenerTls": testAccVirtualGateway_ListenerTLS, "listenerValidation": testAccVirtualGateway_ListenerValidation, + "multiListenerValidation": testAccVirtualGateway_MultiListenerValidation, "logging": testAccVirtualGateway_Logging, "tags": testAccVirtualGateway_Tags, }, @@ -64,12 +72,14 @@ func TestAccAppMesh_serial(t *testing.T) { "listenerTimeout": testAccVirtualNode_listenerTimeout, "listenerTls": testAccVirtualNode_listenerTLS, "listenerValidation": testAccVirtualNode_listenerValidation, + "multiListenerValidation": testAccVirtualNode_multiListenerValidation, "logging": testAccVirtualNode_logging, "tags": testAccVirtualNode_tags, }, "VirtualRouter": { - "basic": testAccVirtualRouter_basic, - "tags": testAccVirtualRouter_tags, + "basic": testAccVirtualRouter_basic, + "multiListener": testAccVirtualRouter_multiListener, + "tags": testAccVirtualRouter_tags, }, "VirtualService": { "virtualNode": testAccVirtualService_virtualNode, diff --git a/internal/service/appmesh/flex.go b/internal/service/appmesh/flex.go index 95a83893dcec..a057fb4def14 100644 --- a/internal/service/appmesh/flex.go +++ b/internal/service/appmesh/flex.go @@ -188,6 +188,10 @@ func expandGRPCRoute(vGrpcRoute []interface{}) *appmesh.GrpcRoute { weightedTarget.Weight = aws.Int64(int64(vWeight)) } + if vPort, ok := mWeightedTarget["port"].(int); ok && vPort > 0 { + weightedTarget.Port = aws.Int64(int64(vPort)) + } + weightedTargets = append(weightedTargets, weightedTarget) } @@ -213,6 +217,10 @@ func expandGRPCRoute(vGrpcRoute []interface{}) *appmesh.GrpcRoute { grpcRouteMatch.ServiceName = aws.String(vServiceName) } + if vPort, ok := mGrpcRouteMatch["port"].(int); ok && vPort > 0 { + grpcRouteMatch.Port = aws.Int64(int64(vPort)) + } + if vGrpcRouteMetadatas, ok := mGrpcRouteMatch["metadata"].(*schema.Set); ok && vGrpcRouteMetadatas.Len() > 0 { grpcRouteMetadatas := []*appmesh.GrpcRouteMetadata{} @@ -352,6 +360,10 @@ func expandHTTPRoute(vHttpRoute []interface{}) *appmesh.HttpRoute { weightedTarget.Weight = aws.Int64(int64(vWeight)) } + if vPort, ok := mWeightedTarget["port"].(int); ok && vPort > 0 { + weightedTarget.Port = aws.Int64(int64(vPort)) + } + weightedTargets = append(weightedTargets, weightedTarget) } @@ -376,6 +388,10 @@ func expandHTTPRoute(vHttpRoute []interface{}) *appmesh.HttpRoute { httpRouteMatch.Scheme = aws.String(vScheme) } + if vPort, ok := mHttpRouteMatch["port"].(int); ok && vPort > 0 { + httpRouteMatch.Port = aws.Int64(int64(vPort)) + } + if vHttpRouteHeaders, ok := mHttpRouteMatch["header"].(*schema.Set); ok && vHttpRouteHeaders.Len() > 0 { httpRouteHeaders := []*appmesh.HttpRouteHeader{} @@ -564,6 +580,10 @@ func expandTCPRoute(vTcpRoute []interface{}) *appmesh.TcpRoute { weightedTarget.Weight = aws.Int64(int64(vWeight)) } + if vPort, ok := mWeightedTarget["port"].(int); ok && vPort > 0 { + weightedTarget.Port = aws.Int64(int64(vPort)) + } + weightedTargets = append(weightedTargets, weightedTarget) } @@ -573,6 +593,17 @@ func expandTCPRoute(vTcpRoute []interface{}) *appmesh.TcpRoute { } } + if vTcpRouteMatch, ok := mTcpRoute["match"].([]interface{}); ok && len(vTcpRouteMatch) > 0 && vTcpRouteMatch[0] != nil { + tcpRouteMatch := &appmesh.TcpRouteMatch{} + + mTcpRouteMatch := vTcpRouteMatch[0].(map[string]interface{}) + + if vPort, ok := mTcpRouteMatch["port"].(int); ok && vPort > 0 { + tcpRouteMatch.Port = aws.Int64(int64(vPort)) + } + tcpRoute.Match = tcpRouteMatch + } + if vTcpTimeout, ok := mTcpRoute["timeout"].([]interface{}); ok { tcpRoute.Timeout = expandTCPTimeout(vTcpTimeout) } @@ -1204,6 +1235,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) @@ -1254,6 +1286,7 @@ func flattenGRPCRoute(grpcRoute *appmesh.GrpcRoute) []interface{} { "metadata": vGrpcRouteMetadatas, "method_name": aws.StringValue(grpcRouteMatch.MethodName), "service_name": aws.StringValue(grpcRouteMatch.ServiceName), + "port": int(aws.Int64Value(grpcRouteMatch.Port)), }, } } @@ -1303,6 +1336,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) @@ -1354,6 +1388,7 @@ func flattenHTTPRoute(httpRoute *appmesh.HttpRoute) []interface{} { "method": aws.StringValue(httpRouteMatch.Method), "prefix": aws.StringValue(httpRouteMatch.Prefix), "scheme": aws.StringValue(httpRouteMatch.Scheme), + "port": int(aws.Int64Value(httpRouteMatch.Port)), }, } } @@ -1436,6 +1471,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) @@ -1446,6 +1482,16 @@ func flattenTCPRoute(tcpRoute *appmesh.TcpRoute) []interface{} { "weighted_target": vWeightedTargets, }, } + + } + + } + + if tcpRouteMatch := tcpRoute.Match; tcpRouteMatch != nil { + mTcpRoute["match"] = []interface{}{ + map[string]interface{}{ + "port": int(aws.Int64Value(tcpRouteMatch.Port)), + }, } } @@ -1502,169 +1548,171 @@ func flattenVirtualNodeSpec(spec *appmesh.VirtualNodeSpec) []interface{} { mSpec["backend_defaults"] = []interface{}{mBackendDefaults} } - if spec.Listeners != nil && spec.Listeners[0] != nil { + if spec.Listeners != nil && len(spec.Listeners) > 0 { + var mListeners []interface{} // Per schema definition, set at most 1 Listener - listener := spec.Listeners[0] - mListener := map[string]interface{}{} + for _, listener := range spec.Listeners { + mListener := map[string]interface{}{} - if connectionPool := listener.ConnectionPool; connectionPool != nil { - mConnectionPool := map[string]interface{}{} + if connectionPool := listener.ConnectionPool; connectionPool != nil { + mConnectionPool := map[string]interface{}{} - if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { - mGrpcConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), + if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { + mGrpcConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), + } + mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} } - mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} - } - 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 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["http"] = []interface{}{mHttpConnectionPool} - } - if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { - mHttp2ConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { + mHttp2ConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + } + mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} } - mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} - } - if tcpConnectionPool := connectionPool.Tcp; tcpConnectionPool != nil { - mTcpConnectionPool := map[string]interface{}{ - "max_connections": int(aws.Int64Value(tcpConnectionPool.MaxConnections)), + if tcpConnectionPool := connectionPool.Tcp; tcpConnectionPool != nil { + mTcpConnectionPool := map[string]interface{}{ + "max_connections": int(aws.Int64Value(tcpConnectionPool.MaxConnections)), + } + mConnectionPool["tcp"] = []interface{}{mTcpConnectionPool} } - mConnectionPool["tcp"] = []interface{}{mTcpConnectionPool} - } - - mListener["connection_pool"] = []interface{}{mConnectionPool} - } - 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} + mListeners = append(mListeners, mListener) } - - mSpec["listener"] = []interface{}{mListener} + mSpec["listener"] = mListeners } if logging := spec.Logging; logging != nil { @@ -1725,18 +1773,20 @@ 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 spec.Listeners != nil && len(spec.Listeners) > 0 { + var mListeners []interface{} + for _, listener := range spec.Listeners { + mListener := 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} + mListeners = append(mListeners, mListener) } - mSpec["listener"] = []interface{}{mListener} + mSpec["listener"] = mListeners } return []interface{}{mSpec} diff --git a/internal/service/appmesh/gateway_route.go b/internal/service/appmesh/gateway_route.go index 2c51ee475210..931a00524f74 100644 --- a/internal/service/appmesh/gateway_route.go +++ b/internal/service/appmesh/gateway_route.go @@ -120,6 +120,11 @@ func ResourceGatewayRoute() *schema.Resource { Type: schema.TypeString, Required: true, }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -282,6 +287,11 @@ func ResourceGatewayRoute() *schema.Resource { "spec.0.http2_route.0.match.0.hostname", }, }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -443,6 +453,11 @@ func ResourceGatewayRoute() *schema.Resource { "spec.0.http_route.0.match.0.hostname", }, }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -766,6 +781,10 @@ func expandGRPCGatewayRoute(vGrpcRoute []interface{}) *appmesh.GrpcGatewayRoute routeMatch.ServiceName = aws.String(vServiceName) } + if vPort, ok := mRouteMatch["port"].(int); ok && vPort > 0 { + routeMatch.Port = aws.Int64(int64(vPort)) + } + route.Match = routeMatch } @@ -826,9 +845,14 @@ func expandHTTPGatewayRouteMatch(vHttpRouteMatch []interface{}) *appmesh.HttpGat if vSuffix, ok := mHostnameMatch["suffix"].(string); ok && vSuffix != "" { hostnameMatch.Suffix = aws.String(vSuffix) } + routeMatch.Hostname = hostnameMatch } + if vPort, ok := mRouteMatch["port"].(int); ok && vPort > 0 { + routeMatch.Port = aws.Int64(int64(vPort)) + } + return routeMatch } @@ -915,6 +939,9 @@ func flattenGRPCGatewayRoute(grpcRoute *appmesh.GrpcGatewayRoute) []interface{} mRouteMatch := map[string]interface{}{ "service_name": aws.StringValue(routeMatch.ServiceName), } + if routeMatch.Port != nil { + mRouteMatch["port"] = int(aws.Int64Value(routeMatch.Port)) + } mGrpcRoute["match"] = []interface{}{mRouteMatch} } @@ -944,6 +971,11 @@ func flattenHTTPGatewayRouteMatch(routeMatch *appmesh.HttpGatewayRouteMatch) []i mRouteMatch["hostname"] = []interface{}{mHostnameMatch} } + + if routeMatch.Port != nil { + mRouteMatch["port"] = int(aws.Int64Value(routeMatch.Port)) + } + return []interface{}{mRouteMatch} } diff --git a/internal/service/appmesh/gateway_route_test.go b/internal/service/appmesh/gateway_route_test.go index eeecd110ea2e..54e9827e2426 100644 --- a/internal/service/appmesh/gateway_route_test.go +++ b/internal/service/appmesh/gateway_route_test.go @@ -160,6 +160,81 @@ func testAccGatewayRoute_GRPCRoute(t *testing.T) { }) } +func testAccGatewayRoute_GRPCRouteWithPort(t *testing.T) { + var v appmesh.GatewayRouteData + resourceName := "aws_appmesh_gateway_route.test" + vs1ResourceName := "aws_appmesh_virtual_service.test.0" + vs2ResourceName := "aws_appmesh_virtual_service.test.1" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + grName := 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: testAccCheckGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGatewayRouteConfig_grpcRouteWithPort(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.grpc_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", "test1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.port", "8080"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_grpcRouteWithPortUpdated(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.grpc_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs2ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", "test2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.port", "8080"), + 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), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccGatewayRoute_HTTPRoute(t *testing.T) { var v appmesh.GatewayRouteData resourceName := "aws_appmesh_gateway_route.test" @@ -287,6 +362,135 @@ func testAccGatewayRoute_HTTPRoute(t *testing.T) { }) } +func testAccGatewayRoute_HTTPRouteWithPort(t *testing.T) { + var v appmesh.GatewayRouteData + resourceName := "aws_appmesh_gateway_route.test" + vs1ResourceName := "aws_appmesh_virtual_service.test.0" + vs2ResourceName := "aws_appmesh_virtual_service.test.1" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + grName := 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: testAccCheckGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGatewayRouteConfig_httpRouteWithPort(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "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, "spec.0.http_route.0.match.0.port", "8080"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_httpRouteWithPortUpdated(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs2ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/users"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.port", "8080"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_httpRouteMatchHostname(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.hostname.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.hostname.0.exact", "test.example.com"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_httpRouteRewrite(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.rewrite.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.rewrite.0.hostname.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.rewrite.0.hostname.0.default_target_hostname", "DISABLED"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.rewrite.0.prefix.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.rewrite.0.prefix.0.default_prefix", "DISABLED"), + 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), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccGatewayRoute_HTTP2Route(t *testing.T) { var v appmesh.GatewayRouteData resourceName := "aws_appmesh_gateway_route.test" @@ -414,6 +618,135 @@ func testAccGatewayRoute_HTTP2Route(t *testing.T) { }) } +func testAccGatewayRoute_HTTP2RouteWithPort(t *testing.T) { + var v appmesh.GatewayRouteData + resourceName := "aws_appmesh_gateway_route.test" + vs1ResourceName := "aws_appmesh_virtual_service.test.0" + vs2ResourceName := "aws_appmesh_virtual_service.test.1" + meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + vgName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + grName := 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: testAccCheckGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGatewayRouteConfig_http2RouteWithPort(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http2_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "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.http2_route.0.match.0.port", "8080"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_http2RouteWithPortUpdated(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http2_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs2ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.prefix", "/users"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.port", "8080"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_http2RouteMatchHostname(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http2_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.hostname.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.hostname.0.exact", "test.example.com"), + 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)), + ), + }, + { + Config: testAccGatewayRouteConfig_http2RouteRewrite(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.virtual_service.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "spec.0.http2_route.0.action.0.target.0.virtual_service.0.virtual_service_name", vs1ResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.rewrite.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.rewrite.0.hostname.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.rewrite.0.hostname.0.default_target_hostname", "DISABLED"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.rewrite.0.prefix.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.rewrite.0.prefix.0.default_prefix", "DISABLED"), + 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), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccGatewayRoute_Tags(t *testing.T) { var v appmesh.GatewayRouteData resourceName := "aws_appmesh_gateway_route.test" @@ -518,7 +851,7 @@ func testAccCheckGatewayRouteExists(name string, v *appmesh.GatewayRouteData) re } } -func testAccGatewayRouteConfigBase(meshName, vgName string) string { +func testAccGatewayRouteConfigBase(meshName, vgName, protocol string) string { return fmt.Sprintf(` resource "aws_appmesh_mesh" "test" { name = %[1]q @@ -532,7 +865,7 @@ resource "aws_appmesh_virtual_gateway" "test" { listener { port_mapping { port = 8080 - protocol = "http" + protocol = "%[3]s" } } } @@ -546,11 +879,11 @@ resource "aws_appmesh_virtual_service" "test" { spec {} } -`, meshName, vgName) +`, meshName, vgName, protocol) } func testAccGatewayRouteConfig_grpcRoute(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "grpc"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -576,7 +909,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_grpcRouteUpdated(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "grpc"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -601,8 +934,62 @@ resource "aws_appmesh_gateway_route" "test" { `, grName)) } +func testAccGatewayRouteConfig_grpcRouteWithPort(meshName, vgName, grName string) string { + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, 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.test[0].name + } + } + } + + match { + service_name = "test1" + port = 8080 + } + } + } +} +`, grName)) +} + +func testAccGatewayRouteConfig_grpcRouteWithPortUpdated(meshName, vgName, grName string) string { + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, 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.test[1].name + } + } + } + + match { + service_name = "test2" + port = 8080 + } + } + } +} +`, grName)) +} + func testAccGatewayRouteConfig_httpRoute(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -628,7 +1015,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_httpRouteUpdated(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -653,8 +1040,62 @@ resource "aws_appmesh_gateway_route" "test" { `, grName)) } +func testAccGatewayRouteConfig_httpRouteWithPort(meshName, vgName, grName string) string { + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, 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.test[0].name + } + } + } + + match { + prefix = "/" + port = 8080 + } + } + } +} +`, grName)) +} + +func testAccGatewayRouteConfig_httpRouteWithPortUpdated(meshName, vgName, grName string) string { + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, 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.test[1].name + } + } + } + + match { + prefix = "/users" + port = 8080 + } + } + } +} +`, grName)) +} + func testAccGatewayRouteConfig_httpRouteMatchHostname(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -682,7 +1123,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_httpRouteRewrite(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -716,7 +1157,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_http2Route(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http2"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -742,7 +1183,60 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_http2RouteUpdated(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, 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.test[1].name + } + } + } + + match { + prefix = "/users" + } + } + } +} +`, grName)) +} + +func testAccGatewayRouteConfig_http2RouteWithPort(meshName, vgName, grName string) string { + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, 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.test[0].name + } + } + } + + match { + prefix = "/" + port = 8080 + } + } + } +} +`, grName)) +} + +func testAccGatewayRouteConfig_http2RouteWithPortUpdated(meshName, vgName, grName string) string { + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http2"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -760,6 +1254,7 @@ resource "aws_appmesh_gateway_route" "test" { match { prefix = "/users" + port = 8080 } } } @@ -768,7 +1263,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_http2RouteMatchHostname(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http2"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -796,7 +1291,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_http2RouteRewrite(meshName, vgName, grName string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http2"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -830,7 +1325,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_tags1(meshName, vgName, grName, tagKey1, tagValue1 string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name @@ -860,7 +1355,7 @@ resource "aws_appmesh_gateway_route" "test" { } func testAccGatewayRouteConfig_tags2(meshName, vgName, grName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccGatewayRouteConfigBase(meshName, vgName, "http"), fmt.Sprintf(` resource "aws_appmesh_gateway_route" "test" { name = %[1]q mesh_name = aws_appmesh_mesh.test.name diff --git a/internal/service/appmesh/route.go b/internal/service/appmesh/route.go index f339d78f1bc5..4f2b27c13c83 100644 --- a/internal/service/appmesh/route.go +++ b/internal/service/appmesh/route.go @@ -99,6 +99,12 @@ func ResourceRoute() *schema.Resource { Required: true, ValidateFunc: validation.IntBetween(0, 100), }, + + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -205,6 +211,12 @@ func ResourceRoute() *schema.Resource { Optional: true, RequiredWith: []string{"spec.0.grpc_route.0.match.0.method_name"}, }, + + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -375,6 +387,12 @@ func ResourceRoute() *schema.Resource { Required: true, ValidateFunc: validation.IntBetween(0, 100), }, + + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -382,6 +400,22 @@ func ResourceRoute() *schema.Resource { }, }, + "match": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, + }, + }, + }, + "timeout": { Type: schema.TypeList, Optional: true, @@ -482,6 +516,12 @@ func RouteHTTPRouteSchema() *schema.Schema { Required: true, ValidateFunc: validation.IntBetween(0, 100), }, + + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, @@ -589,6 +629,12 @@ func RouteHTTPRouteSchema() *schema.Schema { Optional: true, ValidateFunc: validation.StringInSlice(appmesh.HttpScheme_Values(), false), }, + + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 65535), + }, }, }, }, diff --git a/internal/service/appmesh/route_test.go b/internal/service/appmesh/route_test.go index 744173a02a7b..46edfc08d2e9 100644 --- a/internal/service/appmesh/route_test.go +++ b/internal/service/appmesh/route_test.go @@ -229,6 +229,226 @@ func testAccRoute_grpcRoute(t *testing.T) { }) } +func testAccRoute_grpcRouteWithPortMatch(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_grpcRouteWithPortMatch(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.grpc_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.metadata.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.grpc_route.0.match.0.metadata.*", map[string]string{ + "invert": "false", + "match.#": "0", + "name": "X-Testing1", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.method_name", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.*", "deadline-exceeded"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.*", "resource-exhausted"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.*", "server-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.max_retries", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.unit", "s"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.value", "15"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.tcp_retry_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.timeout.#", "0"), + 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)), + ), + }, + { + Config: testAccRouteConfig_grpcRouteWithPortMatchUpdated(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.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.action.0.weighted_target.1.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.metadata.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.grpc_route.0.match.0.metadata.*", map[string]string{ + "invert": "true", + "match.#": "0", + "name": "X-Testing1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.grpc_route.0.match.0.metadata.*", map[string]string{ + "invert": "false", + "match.#": "1", + "match.0.range.#": "1", + "match.0.range.0.end": "7", + "match.0.range.0.start": "2", + "name": "X-Testing2", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.method_name", "test"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", "test.local"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.*", "cancelled"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.*", "gateway-error"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.*", "client-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.max_retries", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.unit", "ms"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.value", "250000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.tcp_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.tcp_retry_events.*", "connection-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.timeout.#", "0"), + 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)), + ), + }, + { + Config: testAccRouteConfig_grpcRouteUpdatedWithZeroWeight(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.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.metadata.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.grpc_route.0.match.0.metadata.*", map[string]string{ + "invert": "true", + "match.#": "0", + "name": "X-Testing1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.grpc_route.0.match.0.metadata.*", map[string]string{ + "invert": "false", + "match.#": "1", + "match.0.range.#": "1", + "match.0.range.0.end": "7", + "match.0.range.0.start": "2", + "name": "X-Testing2", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.method_name", "test"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", "test.local"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.*", "cancelled"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.*", "gateway-error"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.*", "client-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.max_retries", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.unit", "ms"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.value", "250000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.tcp_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.tcp_retry_events.*", "connection-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.timeout.#", "0"), + 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)), + ), + }, + { + Config: testAccRouteConfig_grpcRouteWithMaxRetriesZero(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.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.metadata.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.grpc_route.0.match.0.metadata.*", map[string]string{ + "invert": "false", + "match.#": "0", + "name": "X-Testing1", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.method_name", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.match.0.service_name", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.*", "deadline-exceeded"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.grpc_retry_events.*", "resource-exhausted"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.http_retry_events.*", "server-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.max_retries", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.unit", "s"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.per_retry_timeout.0.value", "15"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.retry_policy.0.tcp_retry_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.grpc_route.0.timeout.#", "0"), + 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_grpcRouteTimeout(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -498,6 +718,127 @@ func testAccRoute_http2Route(t *testing.T) { }) } +func testAccRoute_http2RouteWithPortMatch(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_http2RouteWithPortMatch(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.#", "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.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.header.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.http2_route.0.match.0.header.*", map[string]string{ + "invert": "false", + "match.#": "0", + "name": "X-Testing1", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.method", "POST"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.prefix", "/"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.scheme", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.http_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.http_retry_events.*", "server-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.max_retries", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.per_retry_timeout.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.per_retry_timeout.0.unit", "s"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.per_retry_timeout.0.value", "15"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.tcp_retry_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.timeout.#", "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)), + ), + }, + { + Config: testAccRouteConfig_http2RouteWithPortMatchUpdated(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.#", "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.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.header.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.http2_route.0.match.0.header.*", map[string]string{ + "invert": "true", + "match.#": "0", + "name": "X-Testing1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "spec.0.http2_route.0.match.0.header.*", map[string]string{ + "invert": "false", + "match.#": "1", + "match.0.range.#": "1", + "match.0.range.0.end": "7", + "match.0.range.0.start": "2", + "name": "X-Testing2", + }), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.method", "PUT"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.prefix", "/path"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.scheme", "https"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.match.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.http_retry_events.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.http_retry_events.*", "gateway-error"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.http_retry_events.*", "client-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.max_retries", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.per_retry_timeout.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.per_retry_timeout.0.unit", "ms"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.per_retry_timeout.0.value", "250000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.tcp_retry_events.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.http2_route.0.retry_policy.0.tcp_retry_events.*", "connection-error"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.timeout.#", "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_http2RouteTimeout(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -584,7 +925,118 @@ func testAccRoute_http2RouteTimeout(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.0.timeout.0.per_request.0.value", "5"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.#", "0"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "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" + 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_http(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.#", "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.weighted_target.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.header.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.method", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.scheme", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.retry_policy.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.timeout.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "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)), + ), + }, + { + Config: testAccRouteConfig_httpUpdated(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.#", "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.weighted_target.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.header.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.method", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/path"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.scheme", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.retry_policy.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.timeout.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "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)), + ), + }, + { + Config: testAccRouteConfig_httpUpdatedZeroWeight(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.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.header.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.method", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/path"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.scheme", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.timeout.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.#", "0"), resource.TestCheckResourceAttrSet(resourceName, "created_date"), resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), acctest.CheckResourceAttrAccountID(resourceName, "resource_owner"), @@ -601,7 +1053,7 @@ func testAccRoute_http2RouteTimeout(t *testing.T) { }) } -func testAccRoute_httpRoute(t *testing.T) { +func testAccRoute_httpRouteWithPortMatch(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" meshName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -617,7 +1069,7 @@ func testAccRoute_httpRoute(t *testing.T) { CheckDestroy: testAccCheckRouteDestroy, Steps: []resource.TestStep{ { - Config: testAccRouteConfig_http(meshName, vrName, vn1Name, vn2Name, rName), + Config: testAccRouteConfig_httpWithPortMatch(meshName, vrName, vn1Name, vn2Name, rName), Check: resource.ComposeTestCheckFunc( testAccCheckRouteExists(resourceName, &r), resource.TestCheckResourceAttr(resourceName, "name", rName), @@ -630,11 +1082,13 @@ func testAccRoute_httpRoute(t *testing.T) { 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.http_route.0.match.#", "1"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.header.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.method", ""), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.scheme", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.port", "8080"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.retry_policy.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.timeout.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "0"), @@ -647,7 +1101,7 @@ func testAccRoute_httpRoute(t *testing.T) { ), }, { - Config: testAccRouteConfig_httpUpdated(meshName, vrName, vn1Name, vn2Name, rName), + Config: testAccRouteConfig_httpWithPortMatchUpdated(meshName, vrName, vn1Name, vn2Name, rName), Check: resource.ComposeTestCheckFunc( testAccCheckRouteExists(resourceName, &r), resource.TestCheckResourceAttr(resourceName, "name", rName), @@ -660,11 +1114,14 @@ func testAccRoute_httpRoute(t *testing.T) { 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.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.action.0.weighted_target.1.port", "8080"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.header.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.method", ""), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/path"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.scheme", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.port", "8080"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.retry_policy.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.timeout.#", "0"), resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "0"), @@ -901,6 +1358,107 @@ func testAccRoute_tcpRoute(t *testing.T) { }) } +func testAccRoute_tcpRouteWithPortMatch(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_tcpWithPortMatch(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.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "0"), + 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.tcp_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.match.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.timeout.#", "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)), + ), + }, + { + Config: testAccRouteConfig_tcpWithPortMatchUpdated(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.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http2_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "0"), + 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.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.action.0.weighted_target.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.action.0.weighted_target.1.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.match.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.timeout.#", "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)), + ), + }, + { + Config: testAccRouteConfig_tcpUpdatedZeroWeight(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.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.priority", "0"), + 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.#", "2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.tcp_route.0.timeout.#", "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_tcpRouteTimeout(t *testing.T) { var r appmesh.RouteData resourceName := "aws_appmesh_route.test" @@ -1401,49 +1959,190 @@ func testAccCheckRouteExists(name string, v *appmesh.RouteData) resource.TestChe return err } - *v = *resp.Route + *v = *resp.Route + + return nil + } +} + +func testAccRouteConfigBase(meshName, vrName, vrProtocol, vn1Name, vn2Name 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 = %[3]q + } + } + } +} + +resource "aws_appmesh_virtual_node" "foo" { + name = %[4]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + listener { + port_mapping { + port = 8080 + protocol = %[3]q + } + } + + service_discovery { + dns { + hostname = "foo.simpleapp.local" + } + } + } +} + +resource "aws_appmesh_virtual_node" "bar" { + name = %[5]q + mesh_name = aws_appmesh_mesh.test.id + + spec { + listener { + port_mapping { + port = 8080 + protocol = %[3]q + } + } + + service_discovery { + dns { + hostname = "bar.simpleapp.local" + } + } + } +} +`, meshName, vrName, vrProtocol, vn1Name, vn2Name) +} + +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" { + name = %[1]q + mesh_name = aws_appmesh_mesh.test.id + virtual_router_name = aws_appmesh_virtual_router.test.name + + spec { + grpc_route { + match { + metadata { + name = "X-Testing1" + } + } + + retry_policy { + grpc_retry_events = [ + "deadline-exceeded", + "resource-exhausted", + ] + + http_retry_events = [ + "server-error", + ] + + max_retries = 1 + + per_retry_timeout { + unit = "s" + value = 15 + } + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.foo.name + weight = 100 + } + } + } + } +} +`, rName)) +} + +func testAccRouteConfig_grpcRouteUpdated(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "grpc", vn1Name, vn2Name), 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 { + method_name = "test" + service_name = "test.local" + + metadata { + name = "X-Testing1" + invert = true + } + + metadata { + name = "X-Testing2" + invert = false + + match { + range { + start = 2 + end = 7 + } + } + } + } + + retry_policy { + grpc_retry_events = [ + "cancelled", + ] + + http_retry_events = [ + "client-error", + "gateway-error", + ] + + max_retries = 3 - return nil - } -} + per_retry_timeout { + unit = "ms" + value = 250000 + } -func testAccRouteConfigBase(meshName, vrName, vrProtocol, vn1Name, vn2Name string) string { - return fmt.Sprintf(` -resource "aws_appmesh_mesh" "test" { - name = %[1]q -} + tcp_retry_events = [ + "connection-error", + ] + } -resource "aws_appmesh_virtual_router" "test" { - name = %[2]q - mesh_name = aws_appmesh_mesh.test.id + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.foo.name + weight = 90 + } - spec { - listener { - port_mapping { - port = 8080 - protocol = %[3]q + weighted_target { + virtual_node = aws_appmesh_virtual_node.bar.name + weight = 10 + } } } } } - -resource "aws_appmesh_virtual_node" "foo" { - name = %[4]q - mesh_name = aws_appmesh_mesh.test.id - - spec {} -} - -resource "aws_appmesh_virtual_node" "bar" { - name = %[5]q - mesh_name = aws_appmesh_mesh.test.id - - spec {} -} -`, meshName, vrName, vrProtocol, vn1Name, vn2Name) +`, rName)) } -func testAccRouteConfig_grpcRoute(meshName, vrName, vn1Name, vn2Name, rName string) string { +func testAccRouteConfig_grpcRouteWithPortMatch(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "grpc", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { name = %[1]q @@ -1456,6 +2155,7 @@ resource "aws_appmesh_route" "test" { metadata { name = "X-Testing1" } + port = 8080 } retry_policy { @@ -1480,6 +2180,7 @@ resource "aws_appmesh_route" "test" { weighted_target { virtual_node = aws_appmesh_virtual_node.foo.name weight = 100 + port = 8080 } } } @@ -1488,7 +2189,7 @@ resource "aws_appmesh_route" "test" { `, rName)) } -func testAccRouteConfig_grpcRouteUpdated(meshName, vrName, vn1Name, vn2Name, rName string) string { +func testAccRouteConfig_grpcRouteWithPortMatchUpdated(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "grpc", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { name = %[1]q @@ -1500,7 +2201,7 @@ resource "aws_appmesh_route" "test" { match { method_name = "test" service_name = "test.local" - + port = 8080 metadata { name = "X-Testing1" invert = true @@ -1545,11 +2246,13 @@ resource "aws_appmesh_route" "test" { weighted_target { virtual_node = aws_appmesh_virtual_node.foo.name weight = 90 + port = 8080 } weighted_target { virtual_node = aws_appmesh_virtual_node.bar.name weight = 10 + port = 8080 } } } @@ -1875,6 +2578,114 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_http2RouteWithPortMatch(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http2", vn1Name, vn2Name), 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" + port = 8080 + header { + name = "X-Testing1" + } + } + + retry_policy { + http_retry_events = [ + "server-error", + ] + + max_retries = 1 + + per_retry_timeout { + unit = "s" + value = 15 + } + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.foo.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + +func testAccRouteConfig_http2RouteWithPortMatchUpdated(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http2", vn1Name, vn2Name), 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 = "/path" + method = "PUT" + scheme = "https" + port = 8080 + header { + name = "X-Testing1" + invert = true + } + + header { + name = "X-Testing2" + invert = false + + match { + range { + start = 2 + end = 7 + } + } + } + } + + retry_policy { + http_retry_events = [ + "client-error", + "gateway-error", + ] + + max_retries = 3 + + per_retry_timeout { + unit = "ms" + value = 250000 + } + + tcp_retry_events = [ + "connection-error", + ] + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.foo.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + func testAccRouteConfig_http2RouteWithTimeout(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http2", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { @@ -2011,6 +2822,66 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_httpWithPortMatch(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http", vn1Name, vn2Name), 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 = "/" + port = 8080 + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.foo.name + weight = 100 + port = 8080 + } + } + } + } +} +`, rName)) +} + +func testAccRouteConfig_httpWithPortMatchUpdated(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http", vn1Name, vn2Name), 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 = "/path" + port = 8080 + } + + action { + weighted_target { + virtual_node = aws_appmesh_virtual_node.foo.name + weight = 90 + port = 8080 + } + + weighted_target { + virtual_node = aws_appmesh_virtual_node.bar.name + weight = 10 + port = 8080 + } + } + } + } +} +`, rName)) +} + func testAccRouteConfig_httpUpdatedZeroWeight(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "http", vn1Name, vn2Name), fmt.Sprintf(` resource "aws_appmesh_route" "test" { @@ -2157,6 +3028,62 @@ resource "aws_appmesh_route" "test" { `, rName)) } +func testAccRouteConfig_tcpWithPortMatch(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "tcp", vn1Name, vn2Name), 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.foo.name + weight = 100 + port = 8080 + } + } + match { + port = 8080 + } + } + } +} +`, rName)) +} + +func testAccRouteConfig_tcpWithPortMatchUpdated(meshName, vrName, vn1Name, vn2Name, rName string) string { + return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "tcp", vn1Name, vn2Name), 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.foo.name + weight = 90 + port = 8080 + } + + weighted_target { + virtual_node = aws_appmesh_virtual_node.bar.name + weight = 10 + port = 8080 + } + } + match { + port = 8080 + } + } + } +} +`, rName)) +} + func testAccRouteConfig_tcpUpdatedZeroWeight(meshName, vrName, vn1Name, vn2Name, rName string) string { return acctest.ConfigCompose(testAccRouteConfigBase(meshName, vrName, "tcp", 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 87d0147859de..f376c4ff111b 100644 --- a/internal/service/appmesh/virtual_gateway.go +++ b/internal/service/appmesh/virtual_gateway.go @@ -278,7 +278,6 @@ func ResourceVirtualGateway() *schema.Resource { Type: schema.TypeList, Required: true, MinItems: 1, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "connection_pool": { @@ -302,11 +301,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 +323,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 +339,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 +446,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 +468,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 +483,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 +549,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 +565,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 +1269,143 @@ 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 spec.Listeners != nil && len(spec.Listeners) > 0 { + var mListeners []interface{} + for _, listener := range spec.Listeners { + mListener := map[string]interface{}{} - if connectionPool := listener.ConnectionPool; connectionPool != nil { - mConnectionPool := map[string]interface{}{} + if connectionPool := listener.ConnectionPool; connectionPool != nil { + mConnectionPool := map[string]interface{}{} - if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { - mGrpcConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), + if grpcConnectionPool := connectionPool.Grpc; grpcConnectionPool != nil { + mGrpcConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(grpcConnectionPool.MaxRequests)), + } + mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} } - mConnectionPool["grpc"] = []interface{}{mGrpcConnectionPool} - } - 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 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["http"] = []interface{}{mHttpConnectionPool} - } - if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { - mHttp2ConnectionPool := map[string]interface{}{ - "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + if http2ConnectionPool := connectionPool.Http2; http2ConnectionPool != nil { + mHttp2ConnectionPool := map[string]interface{}{ + "max_requests": int(aws.Int64Value(http2ConnectionPool.MaxRequests)), + } + mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} } - mConnectionPool["http2"] = []interface{}{mHttp2ConnectionPool} - } - mListener["connection_pool"] = []interface{}{mConnectionPool} - } - - 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} + mListeners = append(mListeners, mListener) } - - mSpec["listener"] = []interface{}{mListener} + mSpec["listener"] = mListeners } 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 a6a09fc040a6..a78b7fa8ce1a 100644 --- a/internal/service/appmesh/virtual_gateway_test.go +++ b/internal/service/appmesh/virtual_gateway_test.go @@ -607,6 +607,154 @@ func testAccVirtualGateway_ListenerValidation(t *testing.T) { }) } +func testAccVirtualGateway_MultiListenerValidation(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_multiListenerValidation(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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.0.secret_name", "very-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "abc.example.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "xyz.example.com"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.sds.#", "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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.0.secret_name", "very-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "abc.example.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "xyz.example.com"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.sds.#", "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, + }, + { + Config: testAccVirtualGatewayConfig_multiListenerValidationUpdated(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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.0.secret_name", "very-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.mode", "STRICT"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "abc.example.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "xyz.example.com"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.sds.#", "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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.0.secret_name", "very-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.mode", "STRICT"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "abc.example.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "xyz.example.com"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.sds.#", "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)), + ), + }, + }, + }) +} + func testAccVirtualGateway_Logging(t *testing.T) { var v appmesh.VirtualGatewayData resourceName := "aws_appmesh_virtual_gateway.test" @@ -1213,6 +1361,161 @@ resource "aws_appmesh_virtual_gateway" "test" { `, meshName, vgName) } +func testAccVirtualGatewayConfig_multiListenerValidation(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" + } + + tls { + certificate { + sds { + secret_name = "very-secret" + } + } + + mode = "PERMISSIVE" + + validation { + subject_alternative_names { + match { + exact = ["abc.example.com", "xyz.example.com"] + } + } + + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + + tls { + certificate { + sds { + secret_name = "very-secret" + } + } + + mode = "PERMISSIVE" + + validation { + subject_alternative_names { + match { + exact = ["abc.example.com", "xyz.example.com"] + } + } + + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + } +} + +`, meshName, vgName) +} + +func testAccVirtualGatewayConfig_multiListenerValidationUpdated(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" + } + + tls { + certificate { + sds { + secret_name = "very-secret" + } + } + + mode = "STRICT" + + validation { + subject_alternative_names { + match { + exact = ["abc.example.com", "xyz.example.com"] + } + } + + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + + tls { + certificate { + sds { + secret_name = "very-secret" + } + } + + mode = "STRICT" + + validation { + subject_alternative_names { + match { + exact = ["abc.example.com", "xyz.example.com"] + } + } + + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + } +} +`, meshName, vgName) +} + func testAccVirtualGatewayConfig_logging(meshName, vgName, path string) string { return fmt.Sprintf(` resource "aws_appmesh_mesh" "test" { diff --git a/internal/service/appmesh/virtual_node.go b/internal/service/appmesh/virtual_node.go index 71a9321d3cda..a00cfc5cc37c 100644 --- a/internal/service/appmesh/virtual_node.go +++ b/internal/service/appmesh/virtual_node.go @@ -105,7 +105,6 @@ func ResourceVirtualNode() *schema.Resource { Type: schema.TypeList, Optional: true, MinItems: 0, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "connection_pool": { @@ -129,19 +128,12 @@ 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": { Type: schema.TypeList, Optional: true, MinItems: 0, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "max_connections": { @@ -157,19 +149,12 @@ 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": { Type: schema.TypeList, Optional: true, MinItems: 0, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "max_requests": { @@ -179,19 +164,12 @@ 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": { Type: schema.TypeList, Optional: true, MinItems: 0, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "max_connections": { @@ -201,12 +179,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 +379,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 +431,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 +483,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 +514,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 +547,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 +569,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 +584,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 +650,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 +666,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 33d9687f7e1a..ebd8959d1001 100644 --- a/internal/service/appmesh/virtual_node_test.go +++ b/internal/service/appmesh/virtual_node_test.go @@ -1119,6 +1119,188 @@ func testAccVirtualNode_listenerValidation(t *testing.T) { }) } +func testAccVirtualNode_multiListenerValidation(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_multiListenerValidation(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.0.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.0.secret_name", "very-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "abc.example.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "xyz.example.com"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.sds.#", "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.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.1.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.0.secret_name", "very-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "abc.example.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.0.match.0.exact.*", "xyz.example.com"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.sds.#", "0"), + 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, + }, + { + Config: testAccVirtualNodeConfig_multiListenerValidationUpdated(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.0.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.sds.0.secret_name", "top-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.validation.0.trust.0.sds.0.secret_name", "confidential"), + 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.1.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.certificate.0.sds.0.secret_name", "top-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.tls.0.validation.0.trust.0.sds.0.secret_name", "confidential"), + 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", "8082"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.protocol", "grpc"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.certificate.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.certificate.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.certificate.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.certificate.0.sds.0.secret_name", "top-secret"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.0.subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.0.trust.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.0.trust.0.sds.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.tls.0.validation.0.trust.0.sds.0.secret_name", "confidential"), + 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)), + ), + }, + }, + }) +} + func testAccVirtualNode_logging(t *testing.T) { var vn appmesh.VirtualNodeData resourceName := "aws_appmesh_virtual_node.test" @@ -2103,6 +2285,189 @@ resource "aws_appmesh_virtual_node" "test" { `, vnName)) } +func testAccVirtualNodeConfig_multiListenerValidation(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" + } + + tls { + certificate { + sds { + secret_name = "very-secret" + } + } + + mode = "PERMISSIVE" + + validation { + subject_alternative_names { + match { + exact = ["abc.example.com", "xyz.example.com"] + } + } + + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + + tls { + certificate { + sds { + secret_name = "very-secret" + } + } + + mode = "PERMISSIVE" + + validation { + subject_alternative_names { + match { + exact = ["abc.example.com", "xyz.example.com"] + } + } + + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + + service_discovery { + dns { + hostname = "serviceb.simpleapp.local" + } + } + } +} +`, vnName)) +} + +func testAccVirtualNodeConfig_multiListenerValidationUpdated(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" + } + + tls { + certificate { + sds { + secret_name = "top-secret" + } + } + + mode = "PERMISSIVE" + + validation { + trust { + sds { + secret_name = "confidential" + } + } + } + } + } + + listener { + port_mapping { + port = 8081 + protocol = "http" + } + + tls { + certificate { + sds { + secret_name = "top-secret" + } + } + + mode = "PERMISSIVE" + + validation { + trust { + sds { + secret_name = "confidential" + } + } + } + } + } + + listener { + port_mapping { + port = 8082 + protocol = "grpc" + } + + tls { + certificate { + sds { + secret_name = "top-secret" + } + } + + mode = "PERMISSIVE" + + validation { + trust { + sds { + secret_name = "confidential" + } + } + } + } + } + + service_discovery { + dns { + hostname = "serviceb.simpleapp.local" + } + } + } +} +`, vnName)) +} + func testAccVirtualNodeConfig_logging(meshName, vnName, path string) string { return acctest.ConfigCompose(testAccVirtualNodeConfig_mesh(meshName), fmt.Sprintf(` resource "aws_appmesh_virtual_node" "test" { diff --git a/internal/service/appmesh/virtual_router.go b/internal/service/appmesh/virtual_router.go index dcb20cfd2279..caeb2742a71d 100644 --- a/internal/service/appmesh/virtual_router.go +++ b/internal/service/appmesh/virtual_router.go @@ -66,7 +66,6 @@ func ResourceVirtualRouter() *schema.Resource { Type: schema.TypeList, Required: true, MinItems: 1, - MaxItems: 1, 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 908acbd8745a..411536a2e612 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_multiListener(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_multiListener(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", "http2"), + 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_multiListenerUpdated(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", "8081"), + 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", "8082"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.1.port_mapping.0.protocol", "http2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.2.port_mapping.0.protocol", "grpc"), + ), + }, + { + 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,68 @@ resource "aws_appmesh_virtual_router" "test" { `, meshName, vrName) } +func testAccVirtualRouterConfig_multiListener(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 = "http2" + } + } + } +} +`, meshName, vrName) +} + +func testAccVirtualRouterConfig_multiListenerUpdated(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 = 8081 + protocol = "http" + } + } + listener { + port_mapping { + port = 8082 + protocol = "http2" + } + } + listener { + port_mapping { + port = 8080 + protocol = "grpc" + } + } + } +} +`, meshName, vrName) +} + func testAccVirtualRouterConfig_tags(meshName, vrName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_appmesh_mesh" "test" {