From e02872383695b01857c3900b125fe5fbab8a1679 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 10 Oct 2020 18:34:42 -0400 Subject: [PATCH 1/2] r/aws_appmesh_virtual_gateway: New resource. Acceptance test output: $ make testacc TEST=./aws TESTARGS='-run=TestAccAWSAppmesh/VirtualGateway' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSAppmesh/VirtualGateway -timeout 120m === RUN TestAccAWSAppmesh_serial === RUN TestAccAWSAppmesh_serial/VirtualGateway === RUN TestAccAWSAppmesh_serial/VirtualGateway/tls === RUN TestAccAWSAppmesh_serial/VirtualGateway/backendDefaults === RUN TestAccAWSAppmesh_serial/VirtualGateway/basic === RUN TestAccAWSAppmesh_serial/VirtualGateway/disappears === RUN TestAccAWSAppmesh_serial/VirtualGateway/listenerHealthChecks === RUN TestAccAWSAppmesh_serial/VirtualGateway/logging === RUN TestAccAWSAppmesh_serial/VirtualGateway/tags --- PASS: TestAccAWSAppmesh_serial (281.31s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway (281.31s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/tls (86.76s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/backendDefaults (35.74s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/basic (20.41s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/disappears (17.03s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/listenerHealthChecks (35.32s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/logging (35.34s) --- PASS: TestAccAWSAppmesh_serial/VirtualGateway/tags (50.70s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 281.382s --- aws/internal/service/appmesh/finder/finder.go | 29 + aws/provider.go | 1 + aws/resource_aws_appmesh_mesh_test.go | 1 + aws/resource_aws_appmesh_test.go | 9 + aws/resource_aws_appmesh_virtual_gateway.go | 895 +++++++++++++++++ ...source_aws_appmesh_virtual_gateway_test.go | 934 ++++++++++++++++++ .../r/appmesh_virtual_gateway.html.markdown | 140 +++ 7 files changed, 2009 insertions(+) create mode 100644 aws/internal/service/appmesh/finder/finder.go create mode 100644 aws/resource_aws_appmesh_virtual_gateway.go create mode 100644 aws/resource_aws_appmesh_virtual_gateway_test.go create mode 100644 website/docs/r/appmesh_virtual_gateway.html.markdown diff --git a/aws/internal/service/appmesh/finder/finder.go b/aws/internal/service/appmesh/finder/finder.go new file mode 100644 index 00000000000..46cbb4bdd0b --- /dev/null +++ b/aws/internal/service/appmesh/finder/finder.go @@ -0,0 +1,29 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appmesh" +) + +// 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) { + input := &appmesh.DescribeVirtualGatewayInput{ + MeshName: aws.String(meshName), + VirtualGatewayName: aws.String(virtualGatewayName), + } + if meshOwner != "" { + input.MeshOwner = aws.String(meshOwner) + } + + output, err := conn.DescribeVirtualGateway(input) + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + return output.VirtualGateway, nil +} diff --git a/aws/provider.go b/aws/provider.go index e74e6130eee..2d70a196a43 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -430,6 +430,7 @@ func Provider() *schema.Provider { "aws_appautoscaling_scheduled_action": resourceAwsAppautoscalingScheduledAction(), "aws_appmesh_mesh": resourceAwsAppmeshMesh(), "aws_appmesh_route": resourceAwsAppmeshRoute(), + "aws_appmesh_virtual_gateway": resourceAwsAppmeshVirtualGateway(), "aws_appmesh_virtual_node": resourceAwsAppmeshVirtualNode(), "aws_appmesh_virtual_router": resourceAwsAppmeshVirtualRouter(), "aws_appmesh_virtual_service": resourceAwsAppmeshVirtualService(), diff --git a/aws/resource_aws_appmesh_mesh_test.go b/aws/resource_aws_appmesh_mesh_test.go index eedf78d1f3b..7ac9341fa2a 100644 --- a/aws/resource_aws_appmesh_mesh_test.go +++ b/aws/resource_aws_appmesh_mesh_test.go @@ -21,6 +21,7 @@ func init() { "aws_appmesh_virtual_service", "aws_appmesh_virtual_router", "aws_appmesh_virtual_node", + "aws_appmesh_virtual_gateway", }, }) } diff --git a/aws/resource_aws_appmesh_test.go b/aws/resource_aws_appmesh_test.go index 5b8c18b44b6..70efe696c41 100644 --- a/aws/resource_aws_appmesh_test.go +++ b/aws/resource_aws_appmesh_test.go @@ -25,6 +25,15 @@ func TestAccAWSAppmesh_serial(t *testing.T) { "tcpRouteTimeout": testAccAwsAppmeshRoute_tcpRouteTimeout, "tags": testAccAwsAppmeshRoute_tags, }, + "VirtualGateway": { + "backendDefaults": testAccAwsAppmeshVirtualGateway_BackendDefaults, + "basic": testAccAwsAppmeshVirtualGateway_basic, + "disappears": testAccAwsAppmeshVirtualGateway_disappears, + "listenerHealthChecks": testAccAwsAppmeshVirtualGateway_ListenerHealthChecks, + "logging": testAccAwsAppmeshVirtualGateway_Logging, + "tags": testAccAwsAppmeshVirtualGateway_Tags, + "tls": testAccAwsAppmeshVirtualGateway_TLS, + }, "VirtualNode": { "basic": testAccAwsAppmeshVirtualNode_basic, "backendDefaults": testAccAwsAppmeshVirtualNode_backendDefaults, diff --git a/aws/resource_aws_appmesh_virtual_gateway.go b/aws/resource_aws_appmesh_virtual_gateway.go new file mode 100644 index 00000000000..295b52cdc2d --- /dev/null +++ b/aws/resource_aws_appmesh_virtual_gateway.go @@ -0,0 +1,895 @@ +package aws + +import ( + "fmt" + "log" + "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 resourceAwsAppmeshVirtualGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsAppmeshVirtualGatewayCreate, + Read: resourceAwsAppmeshVirtualGatewayRead, + Update: resourceAwsAppmeshVirtualGatewayUpdate, + Delete: resourceAwsAppmeshVirtualGatewayDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsAppmeshVirtualGatewayImport, + }, + + 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, + }, + + "spec": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "backend_defaults": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_policy": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tls": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enforce": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "ports": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IsPortNumber, + }, + Set: schema.HashInt, + }, + + "validation": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "trust": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "acm": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate_authority_arns": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + Set: schema.HashString, + }, + }, + }, + ConflictsWith: []string{"spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.file"}, + }, + + "file": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate_chain": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + ConflictsWith: []string{"spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.acm"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + "listener": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "health_check": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "healthy_threshold": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(2, 10), + }, + + "interval_millis": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(5000, 300000), + }, + + "path": { + Type: schema.TypeString, + Optional: true, + }, + + "port": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IsPortNumber, + }, + + "protocol": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appmesh.VirtualGatewayPortProtocol_Values(), false), + }, + + "timeout_millis": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(2000, 60000), + }, + + "unhealthy_threshold": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(2, 10), + }, + }, + }, + }, + + "port_mapping": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IsPortNumber, + }, + + "protocol": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appmesh.VirtualGatewayPortProtocol_Values(), false), + }, + }, + }, + }, + + "tls": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "acm": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + }, + }, + ConflictsWith: []string{"spec.0.listener.0.tls.0.certificate.0.file"}, + }, + + "file": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate_chain": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + + "private_key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + ConflictsWith: []string{"spec.0.listener.0.tls.0.certificate.0.acm"}, + }, + }, + }, + }, + + "mode": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appmesh.VirtualGatewayListenerTlsMode_Values(), false), + }, + }, + }, + }, + }, + }, + }, + + "logging": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "access_log": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "file": { + Type: schema.TypeList, + Optional: true, + MinItems: 0, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + "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 resourceAwsAppmeshVirtualGatewayCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + input := &appmesh.CreateVirtualGatewayInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + Spec: expandAppmeshVirtualGatewaySpec(d.Get("spec").([]interface{})), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().AppmeshTags(), + VirtualGatewayName: aws.String(d.Get("name").(string)), + } + if v, ok := d.GetOk("mesh_owner"); ok { + input.MeshOwner = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating App Mesh virtual gateway: %s", input) + output, err := conn.CreateVirtualGateway(input) + + if err != nil { + return fmt.Errorf("error creating App Mesh virtual gateway: %w", err) + } + + d.SetId(aws.StringValue(output.VirtualGateway.Metadata.Uid)) + + return resourceAwsAppmeshVirtualGatewayRead(d, meta) +} + +func resourceAwsAppmeshVirtualGatewayRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + virtualGateway, err := finder.VirtualGateway(conn, d.Get("mesh_name").(string), d.Get("name").(string), d.Get("mesh_owner").(string)) + + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + log.Printf("[WARN] App Mesh virtual gateway (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading App Mesh virtual gateway: %w", err) + } + + if virtualGateway == nil || aws.StringValue(virtualGateway.Status.Status) == appmesh.VirtualGatewayStatusCodeDeleted { + log.Printf("[WARN] App Mesh virtual gateway (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + arn := aws.StringValue(virtualGateway.Metadata.Arn) + d.Set("arn", arn) + d.Set("created_date", virtualGateway.Metadata.CreatedAt.Format(time.RFC3339)) + d.Set("last_updated_date", virtualGateway.Metadata.LastUpdatedAt.Format(time.RFC3339)) + d.Set("mesh_name", virtualGateway.MeshName) + d.Set("mesh_owner", virtualGateway.Metadata.MeshOwner) + d.Set("name", virtualGateway.VirtualGatewayName) + d.Set("resource_owner", virtualGateway.Metadata.ResourceOwner) + err = d.Set("spec", flattenAppmeshVirtualGatewaySpec(virtualGateway.Spec)) + if err != nil { + return fmt.Errorf("error setting spec: %w", err) + } + + tags, err := keyvaluetags.AppmeshListTags(conn, arn) + + if err != nil { + return fmt.Errorf("error listing tags for App Mesh virtual gateway (%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 resourceAwsAppmeshVirtualGatewayUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + if d.HasChange("spec") { + input := &appmesh.UpdateVirtualGatewayInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + Spec: expandAppmeshVirtualGatewaySpec(d.Get("spec").([]interface{})), + VirtualGatewayName: aws.String(d.Get("name").(string)), + } + if v, ok := d.GetOk("mesh_owner"); ok { + input.MeshOwner = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Updating App Mesh virtual gateway: %s", input) + _, err := conn.UpdateVirtualGateway(input) + + if err != nil { + return fmt.Errorf("error updating App Mesh virtual gateway (%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 virtual gateway (%s) tags: %s", arn, err) + } + } + + return resourceAwsAppmeshVirtualGatewayRead(d, meta) +} + +func resourceAwsAppmeshVirtualGatewayDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).appmeshconn + + log.Printf("[DEBUG] Deleting App Mesh virtual gateway (%s)", d.Id()) + _, err := conn.DeleteVirtualGateway(&appmesh.DeleteVirtualGatewayInput{ + MeshName: aws.String(d.Get("mesh_name").(string)), + VirtualGatewayName: aws.String(d.Get("name").(string)), + }) + + if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting App Mesh virtual gateway (%s) : %w", d.Id(), err) + } + + return nil +} + +func resourceAwsAppmeshVirtualGatewayImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 2 { + return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'mesh-name/virtual-gateway-name'", d.Id()) + } + + mesh := parts[0] + name := parts[1] + log.Printf("[DEBUG] Importing App Mesh virtual gateway %s from mesh %s", name, mesh) + + conn := meta.(*AWSClient).appmeshconn + + virtualGateway, err := finder.VirtualGateway(conn, mesh, name, "") + + if err != nil { + return nil, err + } + + d.SetId(aws.StringValue(virtualGateway.Metadata.Uid)) + d.Set("mesh_name", virtualGateway.MeshName) + d.Set("name", virtualGateway.VirtualGatewayName) + + return []*schema.ResourceData{d}, nil +} + +func expandAppmeshVirtualGatewaySpec(vSpec []interface{}) *appmesh.VirtualGatewaySpec { + spec := &appmesh.VirtualGatewaySpec{} + + if len(vSpec) == 0 || vSpec[0] == nil { + // Empty Spec is allowed. + return spec + } + mSpec := vSpec[0].(map[string]interface{}) + + if vBackendDefaults, ok := mSpec["backend_defaults"].([]interface{}); ok && len(vBackendDefaults) > 0 && vBackendDefaults[0] != nil { + backendDefaults := &appmesh.VirtualGatewayBackendDefaults{} + + mBackendDefaults := vBackendDefaults[0].(map[string]interface{}) + + if vClientPolicy, ok := mBackendDefaults["client_policy"].([]interface{}); ok { + backendDefaults.ClientPolicy = expandAppmeshVirtualGatewayClientPolicy(vClientPolicy) + } + + spec.BackendDefaults = backendDefaults + } + + if vListeners, ok := mSpec["listener"].([]interface{}); ok && len(vListeners) > 0 && vListeners[0] != nil { + listeners := []*appmesh.VirtualGatewayListener{} + + for _, vListener := range vListeners { + listener := &appmesh.VirtualGatewayListener{} + + mListener := vListener.(map[string]interface{}) + + if vHealthCheck, ok := mListener["health_check"].([]interface{}); ok && len(vHealthCheck) > 0 && vHealthCheck[0] != nil { + healthCheck := &appmesh.VirtualGatewayHealthCheckPolicy{} + + mHealthCheck := vHealthCheck[0].(map[string]interface{}) + + if vHealthyThreshold, ok := mHealthCheck["healthy_threshold"].(int); ok && vHealthyThreshold > 0 { + healthCheck.HealthyThreshold = aws.Int64(int64(vHealthyThreshold)) + } + if vIntervalMillis, ok := mHealthCheck["interval_millis"].(int); ok && vIntervalMillis > 0 { + healthCheck.IntervalMillis = aws.Int64(int64(vIntervalMillis)) + } + if vPath, ok := mHealthCheck["path"].(string); ok && vPath != "" { + healthCheck.Path = aws.String(vPath) + } + if vPort, ok := mHealthCheck["port"].(int); ok && vPort > 0 { + healthCheck.Port = aws.Int64(int64(vPort)) + } + if vProtocol, ok := mHealthCheck["protocol"].(string); ok && vProtocol != "" { + healthCheck.Protocol = aws.String(vProtocol) + } + if vTimeoutMillis, ok := mHealthCheck["timeout_millis"].(int); ok && vTimeoutMillis > 0 { + healthCheck.TimeoutMillis = aws.Int64(int64(vTimeoutMillis)) + } + if vUnhealthyThreshold, ok := mHealthCheck["unhealthy_threshold"].(int); ok && vUnhealthyThreshold > 0 { + healthCheck.UnhealthyThreshold = aws.Int64(int64(vUnhealthyThreshold)) + } + + listener.HealthCheck = healthCheck + } + + if vPortMapping, ok := mListener["port_mapping"].([]interface{}); ok && len(vPortMapping) > 0 && vPortMapping[0] != nil { + portMapping := &appmesh.VirtualGatewayPortMapping{} + + mPortMapping := vPortMapping[0].(map[string]interface{}) + + if vPort, ok := mPortMapping["port"].(int); ok && vPort > 0 { + portMapping.Port = aws.Int64(int64(vPort)) + } + if vProtocol, ok := mPortMapping["protocol"].(string); ok && vProtocol != "" { + portMapping.Protocol = aws.String(vProtocol) + } + + listener.PortMapping = portMapping + } + + if vTls, ok := mListener["tls"].([]interface{}); ok && len(vTls) > 0 && vTls[0] != nil { + tls := &appmesh.VirtualGatewayListenerTls{} + + mTls := vTls[0].(map[string]interface{}) + + if vMode, ok := mTls["mode"].(string); ok && vMode != "" { + tls.Mode = aws.String(vMode) + } + + if vCertificate, ok := mTls["certificate"].([]interface{}); ok && len(vCertificate) > 0 && vCertificate[0] != nil { + certificate := &appmesh.VirtualGatewayListenerTlsCertificate{} + + mCertificate := vCertificate[0].(map[string]interface{}) + + if vAcm, ok := mCertificate["acm"].([]interface{}); ok && len(vAcm) > 0 && vAcm[0] != nil { + acm := &appmesh.VirtualGatewayListenerTlsAcmCertificate{} + + mAcm := vAcm[0].(map[string]interface{}) + + if vCertificateArn, ok := mAcm["certificate_arn"].(string); ok && vCertificateArn != "" { + acm.CertificateArn = aws.String(vCertificateArn) + } + + certificate.Acm = acm + } + + if vFile, ok := mCertificate["file"].([]interface{}); ok && len(vFile) > 0 && vFile[0] != nil { + file := &appmesh.VirtualGatewayListenerTlsFileCertificate{} + + mFile := vFile[0].(map[string]interface{}) + + if vCertificateChain, ok := mFile["certificate_chain"].(string); ok && vCertificateChain != "" { + file.CertificateChain = aws.String(vCertificateChain) + } + if vPrivateKey, ok := mFile["private_key"].(string); ok && vPrivateKey != "" { + file.PrivateKey = aws.String(vPrivateKey) + } + + certificate.File = file + } + + tls.Certificate = certificate + } + + listener.Tls = tls + } + + listeners = append(listeners, listener) + } + + spec.Listeners = listeners + } + + if vLogging, ok := mSpec["logging"].([]interface{}); ok && len(vLogging) > 0 && vLogging[0] != nil { + logging := &appmesh.VirtualGatewayLogging{} + + mLogging := vLogging[0].(map[string]interface{}) + + if vAccessLog, ok := mLogging["access_log"].([]interface{}); ok && len(vAccessLog) > 0 && vAccessLog[0] != nil { + accessLog := &appmesh.VirtualGatewayAccessLog{} + + mAccessLog := vAccessLog[0].(map[string]interface{}) + + if vFile, ok := mAccessLog["file"].([]interface{}); ok && len(vFile) > 0 && vFile[0] != nil { + file := &appmesh.VirtualGatewayFileAccessLog{} + + mFile := vFile[0].(map[string]interface{}) + + if vPath, ok := mFile["path"].(string); ok && vPath != "" { + file.Path = aws.String(vPath) + } + + accessLog.File = file + } + + logging.AccessLog = accessLog + } + + spec.Logging = logging + } + + return spec +} + +func expandAppmeshVirtualGatewayClientPolicy(vClientPolicy []interface{}) *appmesh.VirtualGatewayClientPolicy { + if len(vClientPolicy) == 0 || vClientPolicy[0] == nil { + return nil + } + + clientPolicy := &appmesh.VirtualGatewayClientPolicy{} + + mClientPolicy := vClientPolicy[0].(map[string]interface{}) + + if vTls, ok := mClientPolicy["tls"].([]interface{}); ok && len(vTls) > 0 && vTls[0] != nil { + tls := &appmesh.VirtualGatewayClientPolicyTls{} + + mTls := vTls[0].(map[string]interface{}) + + if vEnforce, ok := mTls["enforce"].(bool); ok { + tls.Enforce = aws.Bool(vEnforce) + } + + if vPorts, ok := mTls["ports"].(*schema.Set); ok && vPorts.Len() > 0 { + tls.Ports = expandInt64Set(vPorts) + } + + if vValidation, ok := mTls["validation"].([]interface{}); ok && len(vValidation) > 0 && vValidation[0] != nil { + validation := &appmesh.VirtualGatewayTlsValidationContext{} + + mValidation := vValidation[0].(map[string]interface{}) + + if vTrust, ok := mValidation["trust"].([]interface{}); ok && len(vTrust) > 0 && vTrust[0] != nil { + trust := &appmesh.VirtualGatewayTlsValidationContextTrust{} + + mTrust := vTrust[0].(map[string]interface{}) + + if vAcm, ok := mTrust["acm"].([]interface{}); ok && len(vAcm) > 0 && vAcm[0] != nil { + acm := &appmesh.VirtualGatewayTlsValidationContextAcmTrust{} + + mAcm := vAcm[0].(map[string]interface{}) + + if vCertificateAuthorityArns, ok := mAcm["certificate_authority_arns"].(*schema.Set); ok && vCertificateAuthorityArns.Len() > 0 { + acm.CertificateAuthorityArns = expandStringSet(vCertificateAuthorityArns) + } + + trust.Acm = acm + } + + if vFile, ok := mTrust["file"].([]interface{}); ok && len(vFile) > 0 && vFile[0] != nil { + file := &appmesh.VirtualGatewayTlsValidationContextFileTrust{} + + mFile := vFile[0].(map[string]interface{}) + + if vCertificateChain, ok := mFile["certificate_chain"].(string); ok && vCertificateChain != "" { + file.CertificateChain = aws.String(vCertificateChain) + } + + trust.File = file + } + + validation.Trust = trust + } + + tls.Validation = validation + } + + clientPolicy.Tls = tls + } + + return clientPolicy +} + +func flattenAppmeshVirtualGatewaySpec(spec *appmesh.VirtualGatewaySpec) []interface{} { + if spec == nil { + return []interface{}{} + } + + mSpec := map[string]interface{}{} + + if backendDefaults := spec.BackendDefaults; backendDefaults != nil { + mBackendDefaults := map[string]interface{}{ + "client_policy": flattenAppmeshVirtualGatewayClientPolicy(backendDefaults.ClientPolicy), + } + + 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 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} + } + + 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 tls := listener.Tls; tls != nil { + mTls := map[string]interface{}{ + "mode": aws.StringValue(tls.Mode), + } + + 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} + } + + 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} + } + + mTls["certificate"] = []interface{}{mCertificate} + } + + mListener["tls"] = []interface{}{mTls} + } + + mSpec["listener"] = []interface{}{mListener} + } + + if logging := spec.Logging; logging != nil { + mLogging := map[string]interface{}{} + + if accessLog := logging.AccessLog; accessLog != nil { + mAccessLog := map[string]interface{}{} + + if file := accessLog.File; file != nil { + mAccessLog["file"] = []interface{}{ + map[string]interface{}{ + "path": aws.StringValue(file.Path), + }, + } + } + + mLogging["access_log"] = []interface{}{mAccessLog} + } + + mSpec["logging"] = []interface{}{mLogging} + } + + return []interface{}{mSpec} +} + +func flattenAppmeshVirtualGatewayClientPolicy(clientPolicy *appmesh.VirtualGatewayClientPolicy) []interface{} { + if clientPolicy == nil { + return []interface{}{} + } + + mClientPolicy := map[string]interface{}{} + + if tls := clientPolicy.Tls; tls != nil { + mTls := map[string]interface{}{ + "enforce": aws.BoolValue(tls.Enforce), + "ports": flattenInt64Set(tls.Ports), + } + + if validation := tls.Validation; validation != nil { + mValidation := map[string]interface{}{} + + if trust := validation.Trust; trust != nil { + mTrust := map[string]interface{}{} + + if acm := trust.Acm; acm != nil { + mAcm := map[string]interface{}{ + "certificate_authority_arns": flattenStringSet(acm.CertificateAuthorityArns), + } + + mTrust["acm"] = []interface{}{mAcm} + } + + if file := trust.File; file != nil { + mFile := map[string]interface{}{ + "certificate_chain": aws.StringValue(file.CertificateChain), + } + + mTrust["file"] = []interface{}{mFile} + } + + mValidation["trust"] = []interface{}{mTrust} + } + + mTls["validation"] = []interface{}{mValidation} + } + + mClientPolicy["tls"] = []interface{}{mTls} + } + + return []interface{}{mClientPolicy} +} diff --git a/aws/resource_aws_appmesh_virtual_gateway_test.go b/aws/resource_aws_appmesh_virtual_gateway_test.go new file mode 100644 index 00000000000..e238f79cabc --- /dev/null +++ b/aws/resource_aws_appmesh_virtual_gateway_test.go @@ -0,0 +1,934 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/acmpca" + "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource" +) + +func init() { + resource.AddTestSweepers("aws_appmesh_virtual_gateway", &resource.Sweeper{ + Name: "aws_appmesh_virtual_gateway", + F: testSweepAppmeshVirtualGateways, + }) +} + +func testSweepAppmeshVirtualGateways(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) + + log.Printf("[INFO] Deleting App Mesh service mesh (%s) virtual gateway: %s", meshName, virtualGatewayName) + r := resourceAwsAppmeshVirtualGateway() + d := r.Data(nil) + d.SetId("ID") + d.Set("mesh_name", meshName) + d.Set("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 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 testAccAwsAppmeshVirtualGateway_basic(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfig(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshVirtualGateway_disappears(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfig(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsAppmeshVirtualGateway(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAwsAppmeshVirtualGateway_BackendDefaults(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfigBackendDefaults(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", vgName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.enforce", "true"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.ports.#", "1"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.ports.*", "8443"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + Config: testAccAppmeshVirtualGatewayConfigBackendDefaultsUpdated(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", vgName), + resource.TestCheckResourceAttr(resourceName, "spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.enforce", "true"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.ports.#", "2"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.ports.*", "443"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.ports.*", "8443"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.acm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.0.client_policy.0.tls.0.validation.0.trust.0.file.0.certificate_chain", "/etc/ssl/certs/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshVirtualGateway_ListenerHealthChecks(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfigListenerHealthChecks(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.interval_millis", "5000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.path", "/ping"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.protocol", "http2"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.timeout_millis", "2000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.unhealthy_threshold", "5"), + 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", "grpc"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + Config: testAccAppmeshVirtualGatewayConfigListenerHealthChecksUpdated(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.healthy_threshold", "4"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.interval_millis", "7000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.path", ""), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.port", "8081"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.protocol", "grpc"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.timeout_millis", "3000"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.0.unhealthy_threshold", "9"), + 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.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshVirtualGateway_Logging(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfigLogging(meshName, vgName, "/dev/stdout"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.0.access_log.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.0.access_log.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.0.access_log.0.file.0.path", "/dev/stdout"), + 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", meshName, vgName)), + ), + }, + { + Config: testAccAppmeshVirtualGatewayConfigLogging(meshName, vgName, "/tmp/access.log"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(resourceName, "mesh_owner"), + resource.TestCheckResourceAttr(resourceName, "name", vgName), + resource.TestCheckResourceAttr(resourceName, "spec.0.backend_defaults.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.health_check.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.port_mapping.0.protocol", "http"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.0.access_log.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.0.access_log.0.file.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.0.access_log.0.file.0.path", "/tmp/access.log"), + 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", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsAppmeshVirtualGateway_Tags(t *testing.T) { + var v appmesh.VirtualGatewayData + resourceName := "aws_appmesh_virtual_gateway.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfigTags1(meshName, vgName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppmeshVirtualGatewayConfigTags2(meshName, vgName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAppmeshVirtualGatewayConfigTags1(meshName, vgName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccAwsAppmeshVirtualGateway_TLS(t *testing.T) { + var v appmesh.VirtualGatewayData + var ca acmpca.CertificateAuthority + resourceName := "aws_appmesh_virtual_gateway.test" + acmCAResourceName := "aws_acmpca_certificate_authority.test" + acmCertificateResourceName := "aws_acm_certificate.test" + meshName := acctest.RandomWithPrefix("tf-acc-test") + vgName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck("appmesh", t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAppmeshVirtualGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppmeshVirtualGatewayConfigTlsFile(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.#", "1"), + 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.#", "1"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.0.certificate_chain", "/cert_chain.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.0.private_key", "/key.pem"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.mode", "PERMISSIVE"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + // We need to create and activate the CA before issuing a certificate. + { + Config: testAccAppmeshVirtualGatewayConfigRootCA(vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAcmpcaCertificateAuthorityExists(acmCAResourceName, &ca), + testAccCheckAwsAcmpcaCertificateAuthorityActivateCA(&ca), + ), + }, + { + Config: testAccAppmeshVirtualGatewayConfigTlsAcm(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppmeshVirtualGatewayExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "mesh_name", meshName), + testAccCheckResourceAttrAccountID(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.#", "1"), + 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.0.acm.#", "1"), + testAccCheckAppmeshVirtualGatewayTlsAcmCertificateArn(acmCertificateResourceName, "arn", &v), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.certificate.0.file.#", "0"), + resource.TestCheckResourceAttr(resourceName, "spec.0.listener.0.tls.0.mode", "STRICT"), + resource.TestCheckResourceAttr(resourceName, "spec.0.logging.#", "0"), + 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", meshName, vgName)), + ), + }, + { + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s/%s", meshName, vgName), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppmeshVirtualGatewayConfigTlsAcm(meshName, vgName), + Check: resource.ComposeTestCheckFunc( + // CA must be DISABLED for deletion. + testAccCheckAwsAcmpcaCertificateAuthorityDisableCA(&ca), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAppmeshVirtualGatewayDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).appmeshconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appmesh_virtual_node" { + continue + } + + _, err := finder.VirtualGateway(conn, rs.Primary.Attributes["mesh_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 virtual gateway still exists: %s", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAppmeshVirtualGatewayExists(name string, v *appmesh.VirtualGatewayData) 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 virtual gateway ID is set") + } + + out, err := finder.VirtualGateway(conn, rs.Primary.Attributes["mesh_name"], rs.Primary.Attributes["name"], rs.Primary.Attributes["mesh_owner"]) + if err != nil { + return err + } + + *v = *out + + return nil + } +} + +func testAccCheckAppmeshVirtualGatewayTlsAcmCertificateArn(name, key string, v *appmesh.VirtualGatewayData) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + expected, ok := rs.Primary.Attributes[key] + if !ok { + return fmt.Errorf("Key not found: %s", key) + } + if v.Spec == nil || v.Spec.Listeners == nil || len(v.Spec.Listeners) != 1 || v.Spec.Listeners[0].Tls == nil || + v.Spec.Listeners[0].Tls.Certificate == nil || v.Spec.Listeners[0].Tls.Certificate.Acm == nil { + return fmt.Errorf("Not found: v.Spec.Listeners[0].Tls.Certificate.Acm") + } + got := aws.StringValue(v.Spec.Listeners[0].Tls.Certificate.Acm.CertificateArn) + if got != expected { + return fmt.Errorf("Expected ACM certificate ARN %q, got %q", expected, got) + } + + return nil + } +} + +func testAccAppmeshVirtualGatewayConfig(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" + } + } + } +} +`, meshName, vgName) +} + +func testAccAppmeshVirtualGatewayConfigBackendDefaults(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" + } + } + + backend_defaults { + client_policy { + tls { + ports = [8443] + + validation { + trust { + file { + certificate_chain = "/cert_chain.pem" + } + } + } + } + } + } + } +} +`, meshName, vgName) +} + +func testAccAppmeshVirtualGatewayConfigBackendDefaultsUpdated(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" + } + } + + backend_defaults { + client_policy { + tls { + ports = [443, 8443] + + validation { + trust { + file { + certificate_chain = "/etc/ssl/certs/cert_chain.pem" + } + } + } + } + } + } + } +} +`, meshName, vgName) +} + +func testAccAppmeshVirtualGatewayConfigListenerHealthChecks(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 = "grpc" + } + + health_check { + protocol = "http2" + path = "/ping" + healthy_threshold = 3 + unhealthy_threshold = 5 + timeout_millis = 2000 + interval_millis = 5000 + } + } + } +} +`, meshName, vgName) +} + +func testAccAppmeshVirtualGatewayConfigListenerHealthChecksUpdated(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 = 8081 + protocol = "http" + } + + health_check { + protocol = "grpc" + port = 8081 + healthy_threshold = 4 + unhealthy_threshold = 9 + timeout_millis = 3000 + interval_millis = 7000 + } + } + } +} +`, meshName, vgName) +} + +func testAccAppmeshVirtualGatewayConfigLogging(meshName, vgName, path 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" + } + } + + logging { + access_log { + file { + path = %[3]q + } + } + } + } +} +`, meshName, vgName, path) +} + +func testAccAppmeshVirtualGatewayConfigTags1(meshName, vgName, tagKey1, tagValue1 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" + } + } + } + + tags = { + %[3]q = %[4]q + } +} +`, meshName, vgName, tagKey1, tagValue1) +} + +func testAccAppmeshVirtualGatewayConfigTags2(meshName, vgName, tagKey1, tagValue1, tagKey2, tagValue2 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" + } + } + } + + tags = { + %[3]q = %[4]q + %[5]q = %[6]q + } +} +`, meshName, vgName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccAppmeshVirtualGatewayConfigTlsFile(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 { + file { + certificate_chain = "/cert_chain.pem" + private_key = "/key.pem" + } + } + + mode = "PERMISSIVE" + } + } + } +} +`, meshName, vgName) +} + +func testAccAppmeshVirtualGatewayConfigRootCA(rName string) string { + return fmt.Sprintf(` +resource "aws_acmpca_certificate_authority" "test" { + permanent_deletion_time_in_days = 7 + type = "ROOT" + + certificate_authority_configuration { + key_algorithm = "RSA_4096" + signing_algorithm = "SHA512WITHRSA" + + subject { + common_name = "%[1]s.com" + } + } +} +`, rName) +} + +func testAccAppmeshVirtualGatewayConfigTlsAcm(meshName, vgName string) string { + return composeConfig( + testAccAppmeshVirtualGatewayConfigRootCA(vgName), + fmt.Sprintf(` +resource "aws_appmesh_mesh" "test" { + name = %[1]q +} + +resource "aws_acm_certificate" "test" { + domain_name = "test.%[2]s.com" + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn +} + +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 { + acm { + certificate_arn = aws_acm_certificate.test.arn + } + } + + mode = "STRICT" + } + } + } +} +`, meshName, vgName)) +} diff --git a/website/docs/r/appmesh_virtual_gateway.html.markdown b/website/docs/r/appmesh_virtual_gateway.html.markdown new file mode 100644 index 00000000000..ffeeb782348 --- /dev/null +++ b/website/docs/r/appmesh_virtual_gateway.html.markdown @@ -0,0 +1,140 @@ +--- +subcategory: "AppMesh" +layout: "aws" +page_title: "AWS: aws_appmesh_virtual_gateway" +description: |- + Provides an AWS App Mesh virtual gateway resource. +--- + +# Resource: aws_appmesh_virtual_gateway + +Provides an AWS App Mesh virtual gateway resource. + +## Example Usage + +### Basic + +```hcl +resource "aws_appmesh_virtual_gateway" "example" { +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use for the virtual gateway. +* `mesh_name` - (Required) The name of the service mesh in which to create the virtual gateway. +* `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 virtual gateway specification to apply. +* `tags` - (Optional) A map of tags to assign to the resource. + +The `spec` object supports the following: + +* `listener` - (Required) The listeners that the mesh endpoint is expected to receive inbound traffic from. You can specify one listener. +* `backend_defaults` - (Optional) The defaults for backends. +* `logging` - (Optional) The inbound and outbound access logging information for the virtual gateway. + +The `backend_defaults` object supports the following: + +* `client_policy` - (Optional) The default client policy for virtual gateway backends. + +The `client_policy` object supports the following: + +* `tls` - (Optional) The Transport Layer Security (TLS) client policy. + +The `tls` object supports the following: + +* `enforced` - (Optional) Whether the policy is enforced. Default is `true`. +* `ports` - (Optional) One or more ports that the policy is enforced for. +* `validation` - (Required) The TLS validation context. + +The `validation` object supports the following: + +* `trust` - (Required) The TLS validation context trust. + +The `trust` object supports the following: + +* `acm` - (Optional) The TLS validation context trust for an AWS Certificate Manager (ACM) certificate. +* `file` - (Optional) The TLS validation context trust for a local file. + +The `acm` object supports the following: + +* `certificate_authority_arns` - (Required) One or more ACM Amazon Resource Name (ARN)s. + +The `file` object supports the following: + +* `certificate_chain` - (Required) The certificate trust chain for a certificate stored on the file system of the mesh endpoint that the proxy is running on. + +The `listener` object supports the following: + +* `port_mapping` - (Required) The port mapping information for the listener. +* `health_check` - (Optional) The health check information for the listener. +* `tls` - (Optional) The Transport Layer Security (TLS) properties for the listener + +The `logging` object supports the following: + +* `access_log` - (Optional) The access log configuration for a virtual gateway. + +The `access_log` object supports the following: + +* `file` - (Optional) The file object to send virtual gateway access logs to. + +The `file` object supports the following: + +* `path` - (Required) The file path to write access logs to. You can use `/dev/stdout` to send access logs to standard out. + +The `port_mapping` object supports the following: + +* `port` - (Required) The port used for the port mapping. +* `protocol` - (Required) The protocol used for the port mapping. Valid values are `http`, `http2`, `tcp` and `grpc`. + +The `health_check` object supports the following: + +* `healthy_threshold` - (Required) The number of consecutive successful health checks that must occur before declaring listener healthy. +* `interval_millis`- (Required) The time period in milliseconds between each health check execution. +* `protocol` - (Required) The protocol for the health check request. Valid values are `http`, `http2`, and `grpc`. +* `timeout_millis` - (Required) The amount of time to wait when receiving a response from the health check, in milliseconds. +* `unhealthy_threshold` - (Required) The number of consecutive failed health checks that must occur before declaring a virtual gateway unhealthy. +* `path` - (Optional) The destination path for the health check request. This is only required if the specified protocol is `http` or `http2`. +* `port` - (Optional) The destination port for the health check request. This port must match the port defined in the `port_mapping` for the listener. + +The `tls` object supports the following: + +* `certificate` - (Required) The listener's TLS certificate. +* `mode`- (Required) The listener's TLS mode. Valid values: `DISABLED`, `PERMISSIVE`, `STRICT`. + +The `certificate` object supports the following: + +* `acm` - (Optional) An AWS Certificate Manager (ACM) certificate. +* `file` - (optional) A local file certificate. + +The `acm` object supports the following: + +* `certificate_arn` - (Required) The Amazon Resource Name (ARN) for the certificate. + +The `file` object supports the following: + +* `certificate_chain` - (Required) The certificate chain for the certificate. +* `private_key` - (Required) The private key for a certificate stored on the file system of the mesh endpoint that the proxy is running on. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the virtual gateway. +* `arn` - The ARN of the virtual gateway. +* `created_date` - The creation date of the virtual gateway. +* `last_updated_date` - The last update date of the virtual gateway. +* `resource_owner` - The resource owner's AWS account ID. + +## Import + +App Mesh virtual gateway can be imported using `mesh_name` together with the virtual gateway's `name`, +e.g. + +``` +$ terraform import aws_appmesh_virtual_gateway.example mesh/gw1 +``` + +[1]: /docs/providers/aws/index.html From 47fda722477781cf578b7117c520959138f502a2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Oct 2020 08:34:40 -0400 Subject: [PATCH 2/2] r/aws_appmesh_virtual_gateway: Add examples to documentation. --- .../r/appmesh_virtual_gateway.html.markdown | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/website/docs/r/appmesh_virtual_gateway.html.markdown b/website/docs/r/appmesh_virtual_gateway.html.markdown index ffeeb782348..a0f9980337f 100644 --- a/website/docs/r/appmesh_virtual_gateway.html.markdown +++ b/website/docs/r/appmesh_virtual_gateway.html.markdown @@ -16,6 +16,57 @@ Provides an AWS App Mesh virtual gateway resource. ```hcl resource "aws_appmesh_virtual_gateway" "example" { + name = "example-virtual-gateway" + mesh_name = "example-service-mesh" + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + } + } + + tags = { + Environment = "test" + } +} +``` + +### Access Logs and TLS + +```hcl +resource "aws_appmesh_virtual_gateway" "example" { + name = "example-virtual-gateway" + mesh_name = "example-service-mesh" + + spec { + listener { + port_mapping { + port = 8080 + protocol = "http" + } + + tls { + certificate { + acm { + certificate_arn = aws_acm_certificate.example.arn + } + } + + mode = "STRICT" + } + } + + logging { + access_log { + file { + path = "/var/log/access.log" + } + } + } + } } ```