diff --git a/aws/internal/service/appmesh/finder/finder.go b/aws/internal/service/appmesh/finder/finder.go index 46cbb4bdd0b9..2b5eb5879caa 100644 --- a/aws/internal/service/appmesh/finder/finder.go +++ b/aws/internal/service/appmesh/finder/finder.go @@ -5,6 +5,30 @@ import ( "github.com/aws/aws-sdk-go/service/appmesh" ) +// GatewayRoute returns the gateway route corresponding to the specified mesh name, virtual gateway name, gateway route name and optional mesh owner. +// Returns an error if no gateway route is found. +func GatewayRoute(conn *appmesh.AppMesh, meshName, virtualGatewayName, gatewayRouteName, meshOwner string) (*appmesh.GatewayRouteData, error) { + input := &appmesh.DescribeGatewayRouteInput{ + GatewayRouteName: aws.String(gatewayRouteName), + MeshName: aws.String(meshName), + VirtualGatewayName: aws.String(virtualGatewayName), + } + if meshOwner != "" { + input.MeshOwner = aws.String(meshOwner) + } + + output, err := conn.DescribeGatewayRoute(input) + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + return output.GatewayRoute, nil +} + // VirtualGateway returns the virtual gateway corresponding to the specified mesh name, virtual gateway name and optional mesh owner. // Returns an error if no virtual gateway is found. func VirtualGateway(conn *appmesh.AppMesh, meshName, virtualGatewayName, meshOwner string) (*appmesh.VirtualGatewayData, error) { diff --git a/aws/provider.go b/aws/provider.go index 882559a7251d..37b6ee060878 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -428,6 +428,7 @@ func Provider() *schema.Provider { "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), + "aws_appmesh_gateway_route": resourceAwsAppmeshGatewayRoute(), "aws_appmesh_mesh": resourceAwsAppmeshMesh(), "aws_appmesh_route": resourceAwsAppmeshRoute(), "aws_appmesh_virtual_gateway": resourceAwsAppmeshVirtualGateway(), diff --git a/aws/resource_aws_appmesh_gateway_route.go b/aws/resource_aws_appmesh_gateway_route.go new file mode 100644 index 000000000000..940f2d7ea156 --- /dev/null +++ b/aws/resource_aws_appmesh_gateway_route.go @@ -0,0 +1,652 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appmesh/finder" +) + +func resourceAwsAppmeshGatewayRoute() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAppmeshGatewayRouteCreate, + Read: resourceAwsAppmeshGatewayRouteRead, + Update: resourceAwsAppmeshGatewayRouteUpdate, + Delete: resourceAwsAppmeshGatewayRouteDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsAppmeshGatewayRouteImport, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + + "mesh_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + + "mesh_owner": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateAwsAccountId, + }, + + "virtual_gateway_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + + "spec": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "grpc_route": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_service": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_service_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + "match": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{ + "spec.0.grpc_route", + "spec.0.http2_route", + "spec.0.http_route", + }, + }, + + "http2_route": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_service": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_service_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + "match": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^/`), "must start with /"), + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{ + "spec.0.grpc_route", + "spec.0.http2_route", + "spec.0.http_route", + }, + }, + + "http_route": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_service": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_service_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + "match": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^/`), "must start with /"), + }, + }, + }, + }, + }, + }, + ExactlyOneOf: []string{ + "spec.0.grpc_route", + "spec.0.http2_route", + "spec.0.http_route", + }, + }, + }, + }, + }, + + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "created_date": { + Type: schema.TypeString, + Computed: true, + }, + + "last_updated_date": { + Type: schema.TypeString, + Computed: true, + }, + + "resource_owner": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceAwsAppmeshGatewayRouteCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + input := &appmesh.CreateGatewayRouteInput{ + GatewayRouteName: aws.String(d.Get("name").(string)), + MeshName: aws.String(d.Get("mesh_name").(string)), + Spec: expandAppmeshGatewayRouteSpec(d.Get("spec").([]interface{})), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppmeshTags(), + VirtualGatewayName: aws.String(d.Get("virtual_gateway_name").(string)), + } + if v, ok := d.GetOk("mesh_owner"); ok { + input.MeshOwner = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating App Mesh gateway route: %s", input) + output, err := conn.CreateGatewayRoute(input) + + if err != nil { + return fmt.Errorf("error creating App Mesh gateway route: %w", err) + } + + d.SetId(aws.StringValue(output.GatewayRoute.Metadata.Uid)) + + return resourceAwsAppmeshGatewayRouteRead(d, meta) +} + +func resourceAwsAppmeshGatewayRouteRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + gatewayRoute, err := finder.GatewayRoute(conn, d.Get("mesh_name").(string), d.Get("virtual_gateway_name").(string), d.Get("name").(string), d.Get("mesh_owner").(string)) + + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + log.Printf("[WARN] App Mesh gateway route (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading App Mesh gateway route: %w", err) + } + + if gatewayRoute == nil || aws.StringValue(gatewayRoute.Status.Status) == appmesh.GatewayRouteStatusCodeDeleted { + log.Printf("[WARN] App Mesh gateway route (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + arn := aws.StringValue(gatewayRoute.Metadata.Arn) + d.Set("arn", arn) + d.Set("created_date", gatewayRoute.Metadata.CreatedAt.Format(time.RFC3339)) + d.Set("last_updated_date", gatewayRoute.Metadata.LastUpdatedAt.Format(time.RFC3339)) + d.Set("mesh_name", gatewayRoute.MeshName) + d.Set("mesh_owner", gatewayRoute.Metadata.MeshOwner) + d.Set("name", gatewayRoute.GatewayRouteName) + d.Set("resource_owner", gatewayRoute.Metadata.ResourceOwner) + err = d.Set("spec", flattenAppmeshGatewayRouteSpec(gatewayRoute.Spec)) + if err != nil { + return fmt.Errorf("error setting spec: %w", err) + } + d.Set("virtual_gateway_name", gatewayRoute.VirtualGatewayName) + + tags, err := keyvaluetags.AppmeshListTags(conn, arn) + + if err != nil { + return fmt.Errorf("error listing tags for App Mesh gateway route (%s): %s", arn, err) + } + + if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsAppmeshGatewayRouteUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + if d.HasChange("spec") { + input := &appmesh.UpdateGatewayRouteInput{ + GatewayRouteName: aws.String(d.Get("name").(string)), + MeshName: aws.String(d.Get("mesh_name").(string)), + Spec: expandAppmeshGatewayRouteSpec(d.Get("spec").([]interface{})), + VirtualGatewayName: aws.String(d.Get("virtual_gateway_name").(string)), + } + if v, ok := d.GetOk("mesh_owner"); ok { + input.MeshOwner = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Updating App Mesh gateway route: %s", input) + _, err := conn.UpdateGatewayRoute(input) + + if err != nil { + return fmt.Errorf("error updating App Mesh gateway route (%s): %w", d.Id(), err) + } + } + + arn := d.Get("arn").(string) + if d.HasChange("tags") { + o, n := d.GetChange("tags") + + if err := keyvaluetags.AppmeshUpdateTags(conn, arn, o, n); err != nil { + return fmt.Errorf("error updating App Mesh gateway route (%s) tags: %s", arn, err) + } + } + + return resourceAwsAppmeshGatewayRouteRead(d, meta) +} + +func resourceAwsAppmeshGatewayRouteDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + log.Printf("[DEBUG] Deleting App Mesh gateway route (%s)", d.Id()) + _, err := conn.DeleteGatewayRoute(&appmesh.DeleteGatewayRouteInput{ + GatewayRouteName: aws.String(d.Get("name").(string)), + MeshName: aws.String(d.Get("mesh_name").(string)), + VirtualGatewayName: aws.String(d.Get("virtual_gateway_name").(string)), + }) + + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting App Mesh gateway route (%s) : %w", d.Id(), err) + } + + return nil +} + +func resourceAwsAppmeshGatewayRouteImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 3 { + return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'mesh-name/virtual-gateway-name/gateway-route-name'", d.Id()) + } + + mesh := parts[0] + vgName := parts[1] + name := parts[2] + log.Printf("[DEBUG] Importing App Mesh gateway route %s from mesh %s/virtual gateway %s ", name, mesh, vgName) + + conn := meta.(*AWSClient).appmeshconn + + gatewayRoute, err := finder.GatewayRoute(conn, mesh, vgName, name, "") + + if err != nil { + return nil, err + } + + d.SetId(aws.StringValue(gatewayRoute.Metadata.Uid)) + d.Set("mesh_name", gatewayRoute.MeshName) + d.Set("name", gatewayRoute.GatewayRouteName) + d.Set("virtual_gateway_name", gatewayRoute.VirtualGatewayName) + + return []*schema.ResourceData{d}, nil +} + +func expandAppmeshGatewayRouteSpec(vSpec []interface{}) *appmesh.GatewayRouteSpec { + if len(vSpec) == 0 || vSpec[0] == nil { + return nil + } + + spec := &appmesh.GatewayRouteSpec{} + + mSpec := vSpec[0].(map[string]interface{}) + + if vGrpcRoute, ok := mSpec["grpc_route"].([]interface{}); ok { + spec.GrpcRoute = expandAppmeshGrpcGatewayRoute(vGrpcRoute) + } + + if vHttp2Route, ok := mSpec["http2_route"].([]interface{}); ok { + spec.Http2Route = expandAppmeshHttpGatewayRoute(vHttp2Route) + } + + if vHttpRoute, ok := mSpec["http_route"].([]interface{}); ok { + spec.HttpRoute = expandAppmeshHttpGatewayRoute(vHttpRoute) + } + + return spec +} + +func expandAppmeshGatewayRouteTarget(vRouteTarget []interface{}) *appmesh.GatewayRouteTarget { + if len(vRouteTarget) == 0 || vRouteTarget[0] == nil { + return nil + } + + routeTarget := &appmesh.GatewayRouteTarget{} + + mRouteTarget := vRouteTarget[0].(map[string]interface{}) + + if vVirtualService, ok := mRouteTarget["virtual_service"].([]interface{}); ok && len(vVirtualService) > 0 && vVirtualService[0] != nil { + virtualService := &appmesh.GatewayRouteVirtualService{} + + mVirtualService := vVirtualService[0].(map[string]interface{}) + + if vVirtualServiceName, ok := mVirtualService["virtual_service_name"].(string); ok && vVirtualServiceName != "" { + virtualService.VirtualServiceName = aws.String(vVirtualServiceName) + } + + routeTarget.VirtualService = virtualService + } + + return routeTarget +} + +func expandAppmeshGrpcGatewayRoute(vGrpcRoute []interface{}) *appmesh.GrpcGatewayRoute { + if len(vGrpcRoute) == 0 || vGrpcRoute[0] == nil { + return nil + } + + route := &appmesh.GrpcGatewayRoute{} + + mGrpcRoute := vGrpcRoute[0].(map[string]interface{}) + + if vRouteAction, ok := mGrpcRoute["action"].([]interface{}); ok && len(vRouteAction) > 0 && vRouteAction[0] != nil { + routeAction := &appmesh.GrpcGatewayRouteAction{} + + mRouteAction := vRouteAction[0].(map[string]interface{}) + + if vRouteTarget, ok := mRouteAction["target"].([]interface{}); ok { + routeAction.Target = expandAppmeshGatewayRouteTarget(vRouteTarget) + } + + route.Action = routeAction + } + + if vRouteMatch, ok := mGrpcRoute["match"].([]interface{}); ok && len(vRouteMatch) > 0 && vRouteMatch[0] != nil { + routeMatch := &appmesh.GrpcGatewayRouteMatch{} + + mRouteMatch := vRouteMatch[0].(map[string]interface{}) + + if vServiceName, ok := mRouteMatch["service_name"].(string); ok && vServiceName != "" { + routeMatch.ServiceName = aws.String(vServiceName) + } + + route.Match = routeMatch + } + + return route +} + +func expandAppmeshHttpGatewayRoute(vHttpRoute []interface{}) *appmesh.HttpGatewayRoute { + if len(vHttpRoute) == 0 || vHttpRoute[0] == nil { + return nil + } + + route := &appmesh.HttpGatewayRoute{} + + mHttpRoute := vHttpRoute[0].(map[string]interface{}) + + if vRouteAction, ok := mHttpRoute["action"].([]interface{}); ok && len(vRouteAction) > 0 && vRouteAction[0] != nil { + routeAction := &appmesh.HttpGatewayRouteAction{} + + mRouteAction := vRouteAction[0].(map[string]interface{}) + + if vRouteTarget, ok := mRouteAction["target"].([]interface{}); ok { + routeAction.Target = expandAppmeshGatewayRouteTarget(vRouteTarget) + } + + route.Action = routeAction + } + + if vRouteMatch, ok := mHttpRoute["match"].([]interface{}); ok && len(vRouteMatch) > 0 && vRouteMatch[0] != nil { + routeMatch := &appmesh.HttpGatewayRouteMatch{} + + mRouteMatch := vRouteMatch[0].(map[string]interface{}) + + if vPrefix, ok := mRouteMatch["prefix"].(string); ok && vPrefix != "" { + routeMatch.Prefix = aws.String(vPrefix) + } + + route.Match = routeMatch + } + + return route +} + +func flattenAppmeshGatewayRouteSpec(spec *appmesh.GatewayRouteSpec) []interface{} { + if spec == nil { + return []interface{}{} + } + + mSpec := map[string]interface{}{ + "grpc_route": flattenAppmeshGrpcGatewayRoute(spec.GrpcRoute), + "http2_route": flattenAppmeshHttpGatewayRoute(spec.Http2Route), + "http_route": flattenAppmeshHttpGatewayRoute(spec.HttpRoute), + } + + return []interface{}{mSpec} +} + +func flattenAppmeshGatewayRouteTarget(routeTarget *appmesh.GatewayRouteTarget) []interface{} { + if routeTarget == nil { + return []interface{}{} + } + + mRouteTarget := map[string]interface{}{} + + if virtualService := routeTarget.VirtualService; virtualService != nil { + mVirtualService := map[string]interface{}{ + "virtual_service_name": aws.StringValue(virtualService.VirtualServiceName), + } + + mRouteTarget["virtual_service"] = []interface{}{mVirtualService} + } + + return []interface{}{mRouteTarget} +} + +func flattenAppmeshGrpcGatewayRoute(grpcRoute *appmesh.GrpcGatewayRoute) []interface{} { + if grpcRoute == nil { + return []interface{}{} + } + + mGrpcRoute := map[string]interface{}{} + + if routeAction := grpcRoute.Action; routeAction != nil { + mRouteAction := map[string]interface{}{ + "target": flattenAppmeshGatewayRouteTarget(routeAction.Target), + } + + mGrpcRoute["action"] = []interface{}{mRouteAction} + } + + if routeMatch := grpcRoute.Match; routeMatch != nil { + mRouteMatch := map[string]interface{}{ + "service_name": aws.StringValue(routeMatch.ServiceName), + } + + mGrpcRoute["match"] = []interface{}{mRouteMatch} + } + + return []interface{}{mGrpcRoute} +} + +func flattenAppmeshHttpGatewayRoute(httpRoute *appmesh.HttpGatewayRoute) []interface{} { + if httpRoute == nil { + return []interface{}{} + } + + mHttpRoute := map[string]interface{}{} + + if routeAction := httpRoute.Action; routeAction != nil { + mRouteAction := map[string]interface{}{ + "target": flattenAppmeshGatewayRouteTarget(routeAction.Target), + } + + mHttpRoute["action"] = []interface{}{mRouteAction} + } + + if routeMatch := httpRoute.Match; routeMatch != nil { + mRouteMatch := map[string]interface{}{ + "prefix": aws.StringValue(routeMatch.Prefix), + } + + mHttpRoute["match"] = []interface{}{mRouteMatch} + } + + return []interface{}{mHttpRoute} +} diff --git a/aws/resource_aws_appmesh_gateway_route_test.go b/aws/resource_aws_appmesh_gateway_route_test.go new file mode 100644 index 000000000000..2f70755e6ac8 --- /dev/null +++ b/aws/resource_aws_appmesh_gateway_route_test.go @@ -0,0 +1,738 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/appmesh/finder" +) + +func init() { + resource.AddTestSweepers("aws_appmesh_gateway_route", &resource.Sweeper{ + Name: "aws_appmesh_gateway_route", + F: testSweepAppmeshGatewayRoutes, + }) +} + +func testSweepAppmeshGatewayRoutes(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*AWSClient).appmeshconn + + var sweeperErrs *multierror.Error + + err = conn.ListMeshesPages(&appmesh.ListMeshesInput{}, func(page *appmesh.ListMeshesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, mesh := range page.Meshes { + meshName := aws.StringValue(mesh.MeshName) + + err = conn.ListVirtualGatewaysPages(&appmesh.ListVirtualGatewaysInput{MeshName: mesh.MeshName}, func(page *appmesh.ListVirtualGatewaysOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, virtualGateway := range page.VirtualGateways { + virtualGatewayName := aws.StringValue(virtualGateway.VirtualGatewayName) + + err = conn.ListGatewayRoutesPages(&appmesh.ListGatewayRoutesInput{MeshName: mesh.MeshName, VirtualGatewayName: virtualGateway.VirtualGatewayName}, func(page *appmesh.ListGatewayRoutesOutput, isLast bool) bool { + if page == nil { + return !isLast + } + + for _, gatewayRoute := range page.GatewayRoutes { + gatewayRouteName := aws.StringValue(gatewayRoute.GatewayRouteName) + + log.Printf("[INFO] Deleting App Mesh service mesh (%s) virtual gateway (%s) gateway route: %s", meshName, virtualGatewayName, gatewayRouteName) + r := resourceAwsAppmeshGatewayRoute() + d := r.Data(nil) + d.SetId("????????????????") // ID not used in Delete. + d.Set("mesh_name", meshName) + d.Set("name", gatewayRouteName) + d.Set("virtual_gateway_name", virtualGatewayName) + err := r.Delete(d, client) + + if err != nil { + log.Printf("[ERROR] %s", err) + sweeperErrs = multierror.Append(sweeperErrs, err) + continue + } + } + + return !isLast + }) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving App Mesh service mesh (%s) virtual gateway (%s) gateway routes: %w", meshName, virtualGatewayName, err)) + } + } + + return !isLast + }) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving App Mesh service mesh (%s) virtual gateways: %w", meshName, err)) + } + } + + return !isLast + }) + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping Appmesh virtual gateway sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving App Mesh virtual gateways: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func testAccAwsAppmeshGatewayRoute_basic(t *testing.T) { + var v appmesh.GatewayRouteData + resourceName := "aws_appmesh_gateway_route.test" + vsResourceName := "aws_appmesh_virtual_service.test.0" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + grName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshGatewayRouteConfigHttpRoute(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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", vsResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.http_route.0.match.0.prefix", "/"), + resource.TestCheckResourceAttr(resourceName, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAwsAppmeshGatewayRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshGatewayRoute_disappears(t *testing.T) { + var v appmesh.GatewayRouteData + resourceName := "aws_appmesh_gateway_route.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + grName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshGatewayRouteConfigHttpRoute(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppmeshGatewayRoute(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAwsAppmeshGatewayRoute_GrpcRoute(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 := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + grName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshGatewayRouteConfigGrpcRoute(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.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"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + Config: testAccAppmeshGatewayRouteConfigGrpcRouteUpdated(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.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"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAwsAppmeshGatewayRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshGatewayRoute_HttpRoute(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 := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + grName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshGatewayRouteConfigHttpRoute(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + Config: testAccAppmeshGatewayRouteConfigHttpRouteUpdated(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAwsAppmeshGatewayRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshGatewayRoute_Http2Route(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 := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + grName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshGatewayRouteConfigHttp2Route(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + Config: testAccAppmeshGatewayRouteConfigHttp2RouteUpdated(meshName, vgName, grName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.http_route.#", "0"), + resource.TestCheckResourceAttr(resourceName, "virtual_gateway_name", vgName), + resource.TestCheckResourceAttrSet(resourceName, "created_date"), + resource.TestCheckResourceAttrSet(resourceName, "last_updated_date"), + testAccCheckResourceAttrAccountID(resourceName, "resource_owner"), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "appmesh", fmt.Sprintf("mesh/%s/virtualGateway/%s/gatewayRoute/%s", meshName, vgName, grName)), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAwsAppmeshGatewayRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshGatewayRoute_Tags(t *testing.T) { + var v appmesh.GatewayRouteData + resourceName := "aws_appmesh_gateway_route.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + grName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshGatewayRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshGatewayRouteConfigTags1(meshName, vgName, grName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAwsAppmeshGatewayRouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppmeshGatewayRouteConfigTags2(meshName, vgName, grName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAppmeshGatewayRouteConfigTags1(meshName, vgName, grName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshGatewayRouteExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccAwsAppmeshGatewayRouteImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not Found: %s", resourceName) + } + + return fmt.Sprintf("%s/%s/%s", rs.Primary.Attributes["mesh_name"], rs.Primary.Attributes["virtual_gateway_name"], rs.Primary.Attributes["name"]), nil + } +} + +func testAccCheckAppmeshGatewayRouteDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appmeshconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appmesh_gateway_route" { + continue + } + + _, err := finder.GatewayRoute(conn, rs.Primary.Attributes["mesh_name"], rs.Primary.Attributes["virtual_gateway_name"], rs.Primary.Attributes["name"], rs.Primary.Attributes["mesh_owner"]) + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + continue + } + if err != nil { + return err + } + return fmt.Errorf("App Mesh gateway route still exists: %s", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAppmeshGatewayRouteExists(name string, v *appmesh.GatewayRouteData) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appmeshconn + + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No App Mesh gateway route ID is set") + } + + out, err := finder.GatewayRoute(conn, rs.Primary.Attributes["mesh_name"], rs.Primary.Attributes["virtual_gateway_name"], rs.Primary.Attributes["name"], rs.Primary.Attributes["mesh_owner"]) + if err != nil { + return err + } + + *v = *out + + return nil + } +} + +func testAccAppmeshGatewayRouteConfigBase(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.name + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + } +} + +resource "aws_appmesh_virtual_service" "test" { + count = 2 + + name = "%[2]s-${count.index}" + mesh_name = aws_appmesh_mesh.test.name + + spec {} +} +`, meshName, vgName) +} + +func testAccAppmeshGatewayRouteConfigGrpcRoute(meshName, vgName, grName string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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" + } + } + } +} +`, grName)) +} + +func testAccAppmeshGatewayRouteConfigGrpcRouteUpdated(meshName, vgName, grName string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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" + } + } + } +} +`, grName)) +} + +func testAccAppmeshGatewayRouteConfigHttpRoute(meshName, vgName, grName string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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 = "/" + } + } + } +} +`, grName)) +} + +func testAccAppmeshGatewayRouteConfigHttpRouteUpdated(meshName, vgName, grName string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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" + } + } + } +} +`, grName)) +} + +func testAccAppmeshGatewayRouteConfigHttp2Route(meshName, vgName, grName string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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 = "/" + } + } + } +} +`, grName)) +} + +func testAccAppmeshGatewayRouteConfigHttp2RouteUpdated(meshName, vgName, grName string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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 testAccAppmeshGatewayRouteConfigTags1(meshName, vgName, grName, tagKey1, tagValue1 string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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 = "/" + } + } + } + + tags = { + %[2]q = %[3]q + } +} +`, grName, tagKey1, tagValue1)) +} + +func testAccAppmeshGatewayRouteConfigTags2(meshName, vgName, grName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return composeConfig(testAccAppmeshGatewayRouteConfigBase(meshName, vgName), 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 = "/" + } + } + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, grName, tagKey1, tagValue1, tagKey2, tagValue2)) +} diff --git a/aws/resource_aws_appmesh_test.go b/aws/resource_aws_appmesh_test.go index 70efe696c410..1c5079b6b595 100644 --- a/aws/resource_aws_appmesh_test.go +++ b/aws/resource_aws_appmesh_test.go @@ -6,6 +6,14 @@ import ( func TestAccAWSAppmesh_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ + "GatewayRoute": { + "basic": testAccAwsAppmeshGatewayRoute_basic, + "disappears": testAccAwsAppmeshGatewayRoute_disappears, + "grpcRoute": testAccAwsAppmeshGatewayRoute_GrpcRoute, + "httpRoute": testAccAwsAppmeshGatewayRoute_HttpRoute, + "http2Route": testAccAwsAppmeshGatewayRoute_Http2Route, + "tags": testAccAwsAppmeshGatewayRoute_Tags, + }, "Mesh": { "basic": testAccAwsAppmeshMesh_basic, "egressFilter": testAccAwsAppmeshMesh_egressFilter, diff --git a/aws/resource_aws_appmesh_virtual_gateway.go b/aws/resource_aws_appmesh_virtual_gateway.go index 295b52cdc2d6..e00cf4c19e20 100644 --- a/aws/resource_aws_appmesh_virtual_gateway.go +++ b/aws/resource_aws_appmesh_virtual_gateway.go @@ -524,12 +524,12 @@ func resourceAwsAppmeshVirtualGatewayImport(d *schema.ResourceData, meta interfa } func expandAppmeshVirtualGatewaySpec(vSpec []interface{}) *appmesh.VirtualGatewaySpec { - spec := &appmesh.VirtualGatewaySpec{} - if len(vSpec) == 0 || vSpec[0] == nil { - // Empty Spec is allowed. - return spec + return nil } + + spec := &appmesh.VirtualGatewaySpec{} + mSpec := vSpec[0].(map[string]interface{}) if vBackendDefaults, ok := mSpec["backend_defaults"].([]interface{}); ok && len(vBackendDefaults) > 0 && vBackendDefaults[0] != nil { diff --git a/aws/resource_aws_appmesh_virtual_gateway_test.go b/aws/resource_aws_appmesh_virtual_gateway_test.go index e238f79cabc6..6ba6f6ef05cd 100644 --- a/aws/resource_aws_appmesh_virtual_gateway_test.go +++ b/aws/resource_aws_appmesh_virtual_gateway_test.go @@ -20,6 +20,9 @@ func init() { resource.AddTestSweepers("aws_appmesh_virtual_gateway", &resource.Sweeper{ Name: "aws_appmesh_virtual_gateway", F: testSweepAppmeshVirtualGateways, + Dependencies: []string{ + "aws_appmesh_gateway_route", + }, }) } @@ -40,7 +43,7 @@ func testSweepAppmeshVirtualGateways(region string) error { for _, mesh := range page.Meshes { meshName := aws.StringValue(mesh.MeshName) - err := conn.ListVirtualGatewaysPages(&appmesh.ListVirtualGatewaysInput{MeshName: mesh.MeshName}, func(page *appmesh.ListVirtualGatewaysOutput, isLast bool) bool { + err = conn.ListVirtualGatewaysPages(&appmesh.ListVirtualGatewaysInput{MeshName: mesh.MeshName}, func(page *appmesh.ListVirtualGatewaysOutput, isLast bool) bool { if page == nil { return !isLast } @@ -51,7 +54,7 @@ func testSweepAppmeshVirtualGateways(region string) error { log.Printf("[INFO] Deleting App Mesh service mesh (%s) virtual gateway: %s", meshName, virtualGatewayName) r := resourceAwsAppmeshVirtualGateway() d := r.Data(nil) - d.SetId("ID") + d.SetId("????????????????") // ID not used in Delete. d.Set("mesh_name", meshName) d.Set("name", virtualGatewayName) err := r.Delete(d, client) diff --git a/website/docs/r/appmesh_gateway_route.html.markdown b/website/docs/r/appmesh_gateway_route.html.markdown new file mode 100644 index 000000000000..5045c0577e71 --- /dev/null +++ b/website/docs/r/appmesh_gateway_route.html.markdown @@ -0,0 +1,104 @@ +--- +subcategory: "AppMesh" +layout: "aws" +page_title: "AWS: aws_appmesh_gateway_route" +description: |- + Provides an AWS App Mesh gateway route resource. +--- + +# Resource: aws_appmesh_gateway_route + +Provides an AWS App Mesh gateway route resource. + +## Example Usage + +```hcl +resource "aws_appmesh_gateway_route" "example" { + name = "example-gateway-route" + mesh_name = "example-service-mesh" + virtual_gateway_name = aws_appmesh_virtual_gateway.example.name + + spec { + http_route { + action { + target { + virtual_service { + virtual_service_name = aws_appmesh_virtual_service.example.name + } + } + } + + match { + prefix = "/" + } + } + } + + tags = { + Environment = "test" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use for the gateway route. +* `mesh_name` - (Required) The name of the service mesh in which to create the gateway route. +* `virtual_gateway_name` - (Required) The name of the [virtual gateway](/docs/providers/aws/r/appmesh_virtual_gateway.html) to associate the gateway route with. +* `mesh_owner` - (Optional) The AWS account ID of the service mesh's owner. Defaults to the account ID the [AWS provider][1] is currently connected to. +* `spec` - (Required) The gateway route specification to apply. +* `tags` - (Optional) A map of tags to assign to the resource. + +The `spec` object supports the following: + +* `grpc_route` - (Optional) The specification of a gRPC gateway route. +* `http_route` - (Optional) The specification of an HTTP gateway route. +* `http2_route` - (Optional) The specification of an HTTP/2 gateway route. + +The `grpc_route`, `http_route` and `http2_route` objects supports the following: + +* `action` - (Required) The action to take if a match is determined. +* `match` - (Required) The criteria for determining a request match. + +The `grpc_route`, `http_route` and `http2_route`'s `action` object supports the following: + +* `target` - (Required) The target that traffic is routed to when a request matches the gateway route. + +The `target` object supports the following: + +* `virtual_service` - (Required) The virtual service gateway route target. + +The `virtual_service` object supports the following: + +* `virtual_service_name` - (Required) The name of the virtual service that traffic is routed to. + +The `grpc_route`'s `match` object supports the following: + +* `service_name` - (Required) The fully qualified domain name for the service to match from the request. + +The `http_route` and `http2_route`'s `match` object supports the following: + +* `prefix` - (Required) Specifies the path to match requests with. This parameter must always start with `/`, which by itself matches all requests to the virtual service name. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the gateway route. +* `arn` - The ARN of the gateway route. +* `created_date` - The creation date of the gateway route. +* `last_updated_date` - The last update date of the gateway route. +* `resource_owner` - The resource owner's AWS account ID. + +## Import + +App Mesh gateway routes can be imported using `mesh_name` and `virtual_gateway_name` together with the gateway route's `name`, +e.g. + +``` +$ terraform import aws_appmesh_gateway_route.example mesh/gw1/example-gateway-route +``` + +[1]: /docs/providers/aws/index.html