diff --git a/aws/provider.go b/aws/provider.go index 9e101e37870..9e18bbd00e1 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -320,6 +320,7 @@ func Provider() terraform.ResourceProvider { "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), "aws_appmesh_mesh": resourceAwsAppmeshMesh(), + "aws_appmesh_virtual_router": resourceAwsAppmeshVirtualRouter(), "aws_appsync_api_key": resourceAwsAppsyncApiKey(), "aws_appsync_datasource": resourceAwsAppsyncDatasource(), "aws_appsync_graphql_api": resourceAwsAppsyncGraphqlApi(), diff --git a/aws/resource_aws_appmesh_mesh.go b/aws/resource_aws_appmesh_mesh.go index 5c221b420bf..fe9ef34b5eb 100644 --- a/aws/resource_aws_appmesh_mesh.go +++ b/aws/resource_aws_appmesh_mesh.go @@ -94,7 +94,7 @@ func resourceAwsAppmeshMeshRead(d *schema.ResourceData, meta interface{}) error func resourceAwsAppmeshMeshDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).appmeshconn - log.Printf("[DEBUG] App Mesh service mesh: %s", d.Id()) + log.Printf("[DEBUG] Deleting App Mesh service mesh: %s", d.Id()) _, err := conn.DeleteMesh(&appmesh.DeleteMeshInput{ MeshName: aws.String(d.Id()), }) diff --git a/aws/resource_aws_appmesh_test.go b/aws/resource_aws_appmesh_test.go index e7252d66a71..e6b2eaefce1 100644 --- a/aws/resource_aws_appmesh_test.go +++ b/aws/resource_aws_appmesh_test.go @@ -9,6 +9,9 @@ func TestAccAWSAppmesh(t *testing.T) { "Mesh": { "basic": testAccAwsAppmeshMesh_basic, }, + "VirtualRouter": { + "basic": testAccAwsAppmeshVirtualRouter_basic, + }, } for group, m := range testCases { diff --git a/aws/resource_aws_appmesh_virtual_router.go b/aws/resource_aws_appmesh_virtual_router.go new file mode 100644 index 00000000000..bf2414d27e7 --- /dev/null +++ b/aws/resource_aws_appmesh_virtual_router.go @@ -0,0 +1,159 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsAppmeshVirtualRouter() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAppmeshVirtualRouterCreate, + Read: resourceAwsAppmeshVirtualRouterRead, + Update: resourceAwsAppmeshVirtualRouterUpdate, + Delete: resourceAwsAppmeshVirtualRouterDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "mesh_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "spec": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_names": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + }, + }, + + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "created_date": { + Type: schema.TypeString, + Computed: true, + }, + + "last_updated_date": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsAppmeshVirtualRouterCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + req := &appmesh.CreateVirtualRouterInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + VirtualRouterName: aws.String(d.Get("name").(string)), + Spec: expandAppmeshVirtualRouterSpec(d.Get("spec").([]interface{})), + } + + log.Printf("[DEBUG] Creating App Mesh virtual router: %#v", req) + resp, err := conn.CreateVirtualRouter(req) + if err != nil { + return fmt.Errorf("error creating App Mesh virtual router: %s", err) + } + + d.SetId(aws.StringValue(resp.VirtualRouter.Metadata.Uid)) + + return resourceAwsAppmeshVirtualRouterRead(d, meta) +} + +func resourceAwsAppmeshVirtualRouterUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + if d.HasChange("spec") { + _, v := d.GetChange("spec") + req := &appmesh.UpdateVirtualRouterInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + VirtualRouterName: aws.String(d.Get("name").(string)), + Spec: expandAppmeshVirtualRouterSpec(v.([]interface{})), + } + + log.Printf("[DEBUG] Updating App Mesh virtual router: %#v", req) + _, err := conn.UpdateVirtualRouter(req) + if err != nil { + return fmt.Errorf("error updating App Mesh virtual router: %s", err) + } + } + + return resourceAwsAppmeshVirtualRouterRead(d, meta) +} + +func resourceAwsAppmeshVirtualRouterRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + resp, err := conn.DescribeVirtualRouter(&appmesh.DescribeVirtualRouterInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + VirtualRouterName: aws.String(d.Get("name").(string)), + }) + if err != nil { + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + log.Printf("[WARN] App Mesh virtual router (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("error reading App Mesh virtual router: %s", err) + } + if aws.StringValue(resp.VirtualRouter.Status.Status) == appmesh.VirtualRouterStatusCodeDeleted { + log.Printf("[WARN] App Mesh virtual router (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("name", resp.VirtualRouter.VirtualRouterName) + d.Set("mesh_name", resp.VirtualRouter.MeshName) + d.Set("arn", resp.VirtualRouter.Metadata.Arn) + d.Set("created_date", resp.VirtualRouter.Metadata.CreatedAt.Format(time.RFC3339)) + d.Set("last_updated_date", resp.VirtualRouter.Metadata.LastUpdatedAt.Format(time.RFC3339)) + if err := d.Set("spec", flattenAppmeshVirtualRouterSpec(resp.VirtualRouter.Spec)); err != nil { + return err + } + + return nil +} + +func resourceAwsAppmeshVirtualRouterDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + log.Printf("[DEBUG] Deleting App Mesh virtual router: %s", d.Id()) + _, err := conn.DeleteVirtualRouter(&appmesh.DeleteVirtualRouterInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + VirtualRouterName: aws.String(d.Get("name").(string)), + }) + if err != nil { + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + return nil + } + return fmt.Errorf("error deleting App Mesh virtual router: %s", err) + } + + return nil +} diff --git a/aws/resource_aws_appmesh_virtual_router_test.go b/aws/resource_aws_appmesh_virtual_router_test.go new file mode 100644 index 00000000000..8d7cdee0759 --- /dev/null +++ b/aws/resource_aws_appmesh_virtual_router_test.go @@ -0,0 +1,154 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func testAccAwsAppmeshVirtualRouter_basic(t *testing.T) { + var vr appmesh.VirtualRouterData + resourceName := "aws_appmesh_virtual_router.foo" + meshName := fmt.Sprintf("tf-test-mesh-%d", acctest.RandInt()) + vrName := fmt.Sprintf("tf-test-router-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualRouterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualRouterConfig(meshName, vrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualRouterExists( + resourceName, &vr), + resource.TestCheckResourceAttr( + resourceName, "name", vrName), + resource.TestCheckResourceAttr( + resourceName, "mesh_name", meshName), + resource.TestCheckResourceAttr( + resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr( + resourceName, "spec.0.service_names.#", "1"), + resource.TestCheckResourceAttr( + resourceName, "spec.0.service_names.423761483", "serviceb.simpleapp.local"), + resource.TestCheckResourceAttrSet( + resourceName, "created_date"), + resource.TestCheckResourceAttrSet( + resourceName, "last_updated_date"), + resource.TestMatchResourceAttr( + resourceName, "arn", regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:appmesh:[^:]+:\\d{12}:mesh/%s/virtualRouter/%s", meshName, vrName))), + ), + }, + { + Config: testAccAppmeshVirtualRouterConfig_serviceNamesUpdated(meshName, vrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualRouterExists( + resourceName, &vr), + resource.TestCheckResourceAttr( + resourceName, "name", vrName), + resource.TestCheckResourceAttr( + resourceName, "mesh_name", meshName), + resource.TestCheckResourceAttr( + resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr( + resourceName, "spec.0.service_names.#", "2"), + resource.TestCheckResourceAttr( + resourceName, "spec.0.service_names.3826429429", "serviceb1.simpleapp.local"), + resource.TestCheckResourceAttr( + resourceName, "spec.0.service_names.3079206513", "serviceb2.simpleapp.local"), + ), + }, + }, + }) +} + +func testAccCheckAppmeshVirtualRouterDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appmeshconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appmesh_virtual_router" { + continue + } + + _, err := conn.DescribeVirtualRouter(&appmesh.DescribeVirtualRouterInput{ + MeshName: aws.String(rs.Primary.Attributes["mesh_name"]), + VirtualRouterName: aws.String(rs.Primary.Attributes["name"]), + }) + if err != nil { + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + return nil + } + return err + } + return fmt.Errorf("still exist.") + } + + return nil +} + +func testAccCheckAppmeshVirtualRouterExists(name string, v *appmesh.VirtualRouterData) 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 ID is set") + } + + resp, err := conn.DescribeVirtualRouter(&appmesh.DescribeVirtualRouterInput{ + MeshName: aws.String(rs.Primary.Attributes["mesh_name"]), + VirtualRouterName: aws.String(rs.Primary.Attributes["name"]), + }) + if err != nil { + return err + } + + *v = *resp.VirtualRouter + + return nil + } +} + +func testAccAppmeshVirtualRouterConfig(meshName, vrName string) string { + return fmt.Sprintf(` +resource "aws_appmesh_mesh" "foo" { + name = "%s" +} + +resource "aws_appmesh_virtual_router" "foo" { + name = "%s" + mesh_name = "${aws_appmesh_mesh.foo.id}" + + spec { + service_names = ["serviceb.simpleapp.local"] + } +} +`, meshName, vrName) +} + +func testAccAppmeshVirtualRouterConfig_serviceNamesUpdated(meshName, vrName string) string { + return fmt.Sprintf(` +resource "aws_appmesh_mesh" "foo" { + name = "%s" +} + +resource "aws_appmesh_virtual_router" "foo" { + name = "%s" + mesh_name = "${aws_appmesh_mesh.foo.id}" + + spec { + service_names = ["serviceb1.simpleapp.local", "serviceb2.simpleapp.local"] + } +} +`, meshName, vrName) +} diff --git a/aws/structure.go b/aws/structure.go index ec99a849d2c..2c55712f3d7 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil" "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/aws/aws-sdk-go/service/appmesh" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" @@ -4682,3 +4683,34 @@ func flattenRdsScalingConfigurationInfo(scalingConfigurationInfo *rds.ScalingCon return []interface{}{m} } + +func expandAppmeshVirtualRouterSpec(v []interface{}) *appmesh.VirtualRouterSpec { + if len(v) == 0 || v[0] == nil { + return nil + } + + m := v[0].(map[string]interface{}) + spec := &appmesh.VirtualRouterSpec{} + + if s, ok := m["service_names"].(*schema.Set); ok && s.Len() > 0 { + serviceNames := []string{} + for _, n := range s.List() { + serviceNames = append(serviceNames, n.(string)) + } + spec.ServiceNames = aws.StringSlice(serviceNames) + } + + return spec +} + +func flattenAppmeshVirtualRouterSpec(spec *appmesh.VirtualRouterSpec) []interface{} { + if spec == nil || len(spec.ServiceNames) == 0 { + return []interface{}{} + } + + m := map[string]interface{}{ + "service_names": schema.NewSet(schema.HashString, flattenStringList(spec.ServiceNames)), + } + + return []interface{}{m} +} diff --git a/website/aws.erb b/website/aws.erb index 614d1f037c3..62c01613440 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -521,6 +521,9 @@ > aws_appmesh_mesh + > + aws_appmesh_virtual_router + diff --git a/website/docs/r/appmesh_virtual_router.html.markdown b/website/docs/r/appmesh_virtual_router.html.markdown new file mode 100644 index 00000000000..2765457a230 --- /dev/null +++ b/website/docs/r/appmesh_virtual_router.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "aws" +page_title: "AWS: aws_appmesh_virtual_router" +sidebar_current: "docs-aws-resource-appmesh-virtual-router" +description: |- + Provides an AWS App Mesh virtual router resource. +--- + +# aws_appmesh_virtual_router + +Provides an AWS App Mesh virtual router resource. + +## Example Usage + +```hcl +resource "aws_appmesh_virtual_router" "serviceb" { + name = "serviceB" + mesh_name = "simpleapp" + + spec { + service_names = ["serviceb.simpleapp.local"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use for the virtual router. +* `mesh_name` - (Required) The name of the service mesh in which to create the virtual router. +* `spec` - (Required) The virtual router specification to apply. + +The `spec` object supports the following: + +* `service_names` - (Required) The service mesh service names to associate with the virtual router. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the virtual router. +* `arn` - The ARN of the virtual router. +* `created_date` - The creation date of the service mesh. +* `last_updated_date` - The last update date of the service mesh.