diff --git a/.changelog/25189.txt b/.changelog/25189.txt new file mode 100644 index 000000000000..21dc12068b0d --- /dev/null +++ b/.changelog/25189.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_vpc_endpoint_service: Add `supported_ip_address_types` argument +``` + +```release-note:enhancement +data-source/aws_vpc_endpoint_service: Add `supported_ip_address_types` attribute +``` \ No newline at end of file diff --git a/internal/service/ec2/ec2_instance_test.go b/internal/service/ec2/ec2_instance_test.go index dab1e953f04a..ba061c3d9d51 100644 --- a/internal/service/ec2/ec2_instance_test.go +++ b/internal/service/ec2/ec2_instance_test.go @@ -36,6 +36,8 @@ func testAccErrorCheckSkip(t *testing.T) resource.ErrorCheckFunc { "You have reached the maximum allowed number of license configurations created in one day", "specified zone does not support multi-attach-enabled volumes", "Unsupported volume type", + "HostLimitExceeded", + "ReservationCapacityExceeded", ) } diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index 6cafce44adb2..dc04318edfbc 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -25,9 +25,9 @@ const ( errCodeInvalidCapacityReservationIdNotFound = "InvalidCapacityReservationId.NotFound'" ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound" errCodeInvalidClientVPNActiveAssociationNotFound = "InvalidClientVpnActiveAssociationNotFound" - errCodeInvalidClientVPNAssociationIDNotFound = "InvalidClientVpnAssociationIdNotFound" + errCodeInvalidClientVPNAssociationIdNotFound = "InvalidClientVpnAssociationIdNotFound" errCodeInvalidClientVPNAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" - errCodeInvalidClientVPNEndpointIDNotFound = "InvalidClientVpnEndpointId.NotFound" + errCodeInvalidClientVPNEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" errCodeInvalidClientVPNRouteNotFound = "InvalidClientVpnRouteNotFound" ErrCodeInvalidConnectionNotification = "InvalidConnectionNotification" errCodeInvalidConversionTaskIdMalformed = "InvalidConversionTaskId.Malformed" @@ -61,6 +61,7 @@ const ( errCodeInvalidRouteTableIDNotFound = "InvalidRouteTableID.NotFound" errCodeInvalidRouteTableIdNotFound = "InvalidRouteTableId.NotFound" errCodeInvalidSecurityGroupIDNotFound = "InvalidSecurityGroupID.NotFound" + errCodeInvalidServiceName = "InvalidServiceName" errCodeInvalidSnapshotInUse = "InvalidSnapshot.InUse" errCodeInvalidSnapshotNotFound = "InvalidSnapshot.NotFound" ErrCodeInvalidSpotDatafeedNotFound = "InvalidSpotDatafeed.NotFound" @@ -76,9 +77,9 @@ const ( errCodeInvalidTransitGatewayMulticastDomainIdNotFound = "InvalidTransitGatewayMulticastDomainId.NotFound" errCodeInvalidVolumeNotFound = "InvalidVolume.NotFound" errCodeInvalidVPCCIDRBlockAssociationIDNotFound = "InvalidVpcCidrBlockAssociationID.NotFound" - errCodeInvalidVPCEndpointIDNotFound = "InvalidVpcEndpointId.NotFound" + errCodeInvalidVPCEndpointIdNotFound = "InvalidVpcEndpointId.NotFound" errCodeInvalidVPCEndpointNotFound = "InvalidVpcEndpoint.NotFound" - errCodeInvalidVPCEndpointServiceIDNotFound = "InvalidVpcEndpointServiceId.NotFound" + errCodeInvalidVPCEndpointServiceIdNotFound = "InvalidVpcEndpointServiceId.NotFound" errCodeInvalidVPCIDNotFound = "InvalidVpcID.NotFound" errCodeInvalidVPCPeeringConnectionIDNotFound = "InvalidVpcPeeringConnectionID.NotFound" errCodeInvalidVPNConnectionIDNotFound = "InvalidVpnConnectionID.NotFound" diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 33dd7b277076..339fe690d83a 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -210,7 +210,7 @@ func FindClientVPNEndpoints(conn *ec2.EC2, input *ec2.DescribeClientVpnEndpoints return !lastPage }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -303,7 +303,7 @@ func FindClientVPNAuthorizationRules(conn *ec2.EC2, input *ec2.DescribeClientVpn return !lastPage }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -369,7 +369,7 @@ func FindClientVPNNetworkAssociations(conn *ec2.EC2, input *ec2.DescribeClientVp return !lastPage }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound, errCodeInvalidClientVPNAssociationIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound, errCodeInvalidClientVPNAssociationIdNotFound) { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -449,7 +449,7 @@ func FindClientVPNRoutes(conn *ec2.EC2, input *ec2.DescribeClientVpnRoutesInput) return !lastPage }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -2560,7 +2560,7 @@ func FindVPCEndpoints(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) ([]*e return !lastPage }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIdNotFound) { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -2574,9 +2574,9 @@ func FindVPCEndpoints(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) ([]*e return output, nil } -func FindVPCEndpointByID(conn *ec2.EC2, vpcEndpointID string) (*ec2.VpcEndpoint, error) { +func FindVPCEndpointByID(conn *ec2.EC2, id string) (*ec2.VpcEndpoint, error) { input := &ec2.DescribeVpcEndpointsInput{ - VpcEndpointIds: aws.StringSlice([]string{vpcEndpointID}), + VpcEndpointIds: aws.StringSlice([]string{id}), } output, err := FindVPCEndpoint(conn, input) @@ -2593,7 +2593,7 @@ func FindVPCEndpointByID(conn *ec2.EC2, vpcEndpointID string) (*ec2.VpcEndpoint, } // Eventual consistency check. - if aws.StringValue(output.VpcEndpointId) != vpcEndpointID { + if aws.StringValue(output.VpcEndpointId) != id { return nil, &resource.NotFoundError{ LastRequest: input, } @@ -2602,6 +2602,176 @@ func FindVPCEndpointByID(conn *ec2.EC2, vpcEndpointID string) (*ec2.VpcEndpoint, return output, nil } +func FindVPCEndpointServiceConfiguration(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServiceConfigurationsInput) (*ec2.ServiceConfiguration, error) { + output, err := FindVPCEndpointServiceConfigurations(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVPCEndpointServiceConfigurations(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServiceConfigurationsInput) ([]*ec2.ServiceConfiguration, error) { + var output []*ec2.ServiceConfiguration + + err := conn.DescribeVpcEndpointServiceConfigurationsPages(input, func(page *ec2.DescribeVpcEndpointServiceConfigurationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ServiceConfigurations { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCEndpointServices(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServicesInput) ([]*ec2.ServiceDetail, []string, error) { + var serviceDetails []*ec2.ServiceDetail + var serviceNames []string + + err := describeVPCEndpointServicesPages(conn, input, func(page *ec2.DescribeVpcEndpointServicesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ServiceDetails { + if v != nil { + serviceDetails = append(serviceDetails, v) + } + } + + for _, v := range page.ServiceNames { + serviceNames = append(serviceNames, aws.StringValue(v)) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidServiceName) { + return nil, nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, nil, err + } + + return serviceDetails, serviceNames, nil +} + +func FindVPCEndpointServiceConfigurationByID(conn *ec2.EC2, id string) (*ec2.ServiceConfiguration, error) { + input := &ec2.DescribeVpcEndpointServiceConfigurationsInput{ + ServiceIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPCEndpointServiceConfiguration(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.ServiceState); state == ec2.ServiceStateDeleted || state == ec2.ServiceStateFailed { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.ServiceId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPCEndpointServicePermissions(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServicePermissionsInput) ([]*ec2.AllowedPrincipal, error) { + var output []*ec2.AllowedPrincipal + + err := conn.DescribeVpcEndpointServicePermissionsPages(input, func(page *ec2.DescribeVpcEndpointServicePermissionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.AllowedPrincipals { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCEndpointServicePermissionsByID(conn *ec2.EC2, id string) ([]*ec2.AllowedPrincipal, error) { + input := &ec2.DescribeVpcEndpointServicePermissionsInput{ + ServiceId: aws.String(id), + } + + return FindVPCEndpointServicePermissions(conn, input) +} + +func FindVPCEndpointServicePermissionExists(conn *ec2.EC2, serviceID, principalARN string) error { + allowedPrincipals, err := FindVPCEndpointServicePermissionsByID(conn, serviceID) + + if err != nil { + return err + } + + for _, v := range allowedPrincipals { + if aws.StringValue(v.Principal) == principalARN { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint Service (%s) Principal (%s) not found", serviceID, principalARN), + } +} + // FindVPCEndpointRouteTableAssociationExists returns NotFoundError if no association for the specified VPC endpoint and route table IDs is found. func FindVPCEndpointRouteTableAssociationExists(conn *ec2.EC2, vpcEndpointID string, routeTableID string) error { vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) diff --git a/internal/service/ec2/generate.go b/internal/service/ec2/generate.go index f85c6ad795a7..987c7c0e0ace 100644 --- a/internal/service/ec2/generate.go +++ b/internal/service/ec2/generate.go @@ -1,7 +1,7 @@ //go:generate go run ../../generate/tagresource/main.go -IDAttribName=resource_id //go:generate go run ../../generate/tags/main.go -GetTag -ListTags -ListTagsOp=DescribeTags -ListTagsInFiltIDName=resource-id -ListTagsInIDElem=Resources -ServiceTagsSlice -TagOp=CreateTags -TagInIDElem=Resources -TagInIDNeedSlice=yes -TagType2=TagDescription -UntagOp=DeleteTags -UntagInNeedTagType -UntagInTagsElem=Tags -UpdateTags //go:generate go run generate/createtags/main.go -//go:generate go run ../../generate/listpages/main.go -ListOps=DescribeSpotFleetInstances,DescribeSpotFleetRequestHistory +//go:generate go run ../../generate/listpages/main.go -ListOps=DescribeSpotFleetInstances,DescribeSpotFleetRequestHistory,DescribeVpcEndpointServices // ONLY generate directives and package declaration! Do not add anything else to this file. package ec2 diff --git a/internal/service/ec2/list_pages_gen.go b/internal/service/ec2/list_pages_gen.go index d8acffa16bed..c19d4b5bc723 100644 --- a/internal/service/ec2/list_pages_gen.go +++ b/internal/service/ec2/list_pages_gen.go @@ -1,4 +1,4 @@ -// Code generated by "internal/generate/listpages/main.go -ListOps=DescribeSpotFleetInstances,DescribeSpotFleetRequestHistory"; DO NOT EDIT. +// Code generated by "internal/generate/listpages/main.go -ListOps=DescribeSpotFleetInstances,DescribeSpotFleetRequestHistory,DescribeVpcEndpointServices"; DO NOT EDIT. package ec2 @@ -50,3 +50,24 @@ func describeSpotFleetRequestHistoryPagesWithContext(ctx context.Context, conn * } return nil } + +func describeVPCEndpointServicesPages(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServicesInput, fn func(*ec2.DescribeVpcEndpointServicesOutput, bool) bool) error { + return describeVPCEndpointServicesPagesWithContext(context.Background(), conn, input, fn) +} + +func describeVPCEndpointServicesPagesWithContext(ctx context.Context, conn *ec2.EC2, input *ec2.DescribeVpcEndpointServicesInput, fn func(*ec2.DescribeVpcEndpointServicesOutput, bool) bool) error { + for { + output, err := conn.DescribeVpcEndpointServicesWithContext(ctx, input) + if err != nil { + return err + } + + lastPage := aws.StringValue(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index 4036328fa89c..63517bf5e1a2 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -1103,6 +1103,41 @@ func StatusVPCEndpointState(conn *ec2.EC2, id string) resource.StateRefreshFunc } } +func StatusVPCEndpointServiceStateAvailable(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + // Don't call FindVPCEndpointServiceConfigurationByID as it maps useful status codes to NotFoundError. + output, err := FindVPCEndpointServiceConfiguration(conn, &ec2.DescribeVpcEndpointServiceConfigurationsInput{ + ServiceIds: aws.StringSlice([]string{id}), + }) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.ServiceState), nil + } +} + +func StatusVPCEndpointServiceStateDeleted(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindVPCEndpointServiceConfigurationByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.ServiceState), nil + } +} + const ( VPCEndpointRouteTableAssociationStatusReady = "ready" ) diff --git a/internal/service/ec2/vpc_endpoint_connection_accepter.go b/internal/service/ec2/vpc_endpoint_connection_accepter.go index 0f4e7a891071..52ff607eb0a9 100644 --- a/internal/service/ec2/vpc_endpoint_connection_accepter.go +++ b/internal/service/ec2/vpc_endpoint_connection_accepter.go @@ -115,7 +115,7 @@ func resourceVPCEndpointConnectionAccepterDelete(d *schema.ResourceData, meta in _, err = conn.RejectVpcEndpointConnections(input) - if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { return nil } diff --git a/internal/service/ec2/vpc_endpoint_route_table_association.go b/internal/service/ec2/vpc_endpoint_route_table_association.go index 9c0aaa3594bf..f7c2d88dd321 100644 --- a/internal/service/ec2/vpc_endpoint_route_table_association.go +++ b/internal/service/ec2/vpc_endpoint_route_table_association.go @@ -107,7 +107,7 @@ func resourceVPCEndpointRouteTableAssociationDelete(d *schema.ResourceData, meta log.Printf("[DEBUG] Deleting VPC Endpoint Route Table Association: %s", id) _, err := conn.ModifyVpcEndpoint(input) - if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIDNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidRouteTableIdNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidParameter) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIdNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidRouteTableIdNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidParameter) { return nil } diff --git a/internal/service/ec2/vpc_endpoint_security_group_association.go b/internal/service/ec2/vpc_endpoint_security_group_association.go index 93aededc84c3..9ae8db53c469 100644 --- a/internal/service/ec2/vpc_endpoint_security_group_association.go +++ b/internal/service/ec2/vpc_endpoint_security_group_association.go @@ -183,7 +183,7 @@ func deleteVPCEndpointSecurityGroupAssociation(conn *ec2.EC2, vpcEndpointID, sec log.Printf("[DEBUG] Deleting VPC Endpoint Security Group Association: %s", input) _, err := conn.ModifyVpcEndpoint(input) - if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIDNotFound, errCodeInvalidGroupNotFound, errCodeInvalidParameter) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIdNotFound, errCodeInvalidGroupNotFound, errCodeInvalidParameter) { return nil } diff --git a/internal/service/ec2/vpc_endpoint_service.go b/internal/service/ec2/vpc_endpoint_service.go index 5e38bebacaa5..7f2fca6f5380 100644 --- a/internal/service/ec2/vpc_endpoint_service.go +++ b/internal/service/ec2/vpc_endpoint_service.go @@ -1,7 +1,6 @@ package ec2 import ( - "errors" "fmt" "log" "time" @@ -10,11 +9,12 @@ import ( "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -24,6 +24,7 @@ func ResourceVPCEndpointService() *schema.Resource { Read: resourceVPCEndpointServiceRead, Update: resourceVPCEndpointServiceUpdate, Delete: resourceVPCEndpointServiceDelete, + Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -38,7 +39,6 @@ func ResourceVPCEndpointService() *schema.Resource { Optional: true, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, }, "arn": { Type: schema.TypeString, @@ -48,13 +48,11 @@ func ResourceVPCEndpointService() *schema.Resource { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "base_endpoint_dns_names": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "gateway_load_balancer_arns": { Type: schema.TypeSet, @@ -64,7 +62,6 @@ func ResourceVPCEndpointService() *schema.Resource { Type: schema.TypeString, ValidateFunc: verify.ValidARN, }, - Set: schema.HashString, }, "manages_vpc_endpoints": { Type: schema.TypeBool, @@ -78,7 +75,6 @@ func ResourceVPCEndpointService() *schema.Resource { Type: schema.TypeString, ValidateFunc: verify.ValidARN, }, - Set: schema.HashString, }, "private_dns_name": { Type: schema.TypeString, @@ -121,10 +117,25 @@ func ResourceVPCEndpointService() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "supported_ip_address_types": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"ipv4", "ipv6"}, false), + }, + }, "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + CustomizeDiff: verify.SetTagsDiff, } } @@ -134,46 +145,48 @@ func resourceVPCEndpointServiceCreate(d *schema.ResourceData, meta interface{}) defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - req := &ec2.CreateVpcEndpointServiceConfigurationInput{ + input := &ec2.CreateVpcEndpointServiceConfigurationInput{ AcceptanceRequired: aws.Bool(d.Get("acceptance_required").(bool)), - TagSpecifications: tagSpecificationsFromKeyValueTags(tags, "vpc-endpoint-service"), + TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeVpcEndpointService), } - if v, ok := d.GetOk("private_dns_name"); ok { - req.PrivateDnsName = aws.String(v.(string)) + + if v, ok := d.GetOk("gateway_load_balancer_arns"); ok && v.(*schema.Set).Len() > 0 { + input.GatewayLoadBalancerArns = flex.ExpandStringSet(v.(*schema.Set)) } - if v, ok := d.GetOk("gateway_load_balancer_arns"); ok { - if v, ok := v.(*schema.Set); ok && v.Len() > 0 { - req.GatewayLoadBalancerArns = flex.ExpandStringSet(v) - } + if v, ok := d.GetOk("network_load_balancer_arns"); ok && v.(*schema.Set).Len() > 0 { + input.NetworkLoadBalancerArns = flex.ExpandStringSet(v.(*schema.Set)) } - if v, ok := d.GetOk("network_load_balancer_arns"); ok { - if v, ok := v.(*schema.Set); ok && v.Len() > 0 { - req.NetworkLoadBalancerArns = flex.ExpandStringSet(v) - } + if v, ok := d.GetOk("private_dns_name"); ok { + input.PrivateDnsName = aws.String(v.(string)) } - log.Printf("[DEBUG] Creating VPC Endpoint Service configuration: %#v", req) - resp, err := conn.CreateVpcEndpointServiceConfiguration(req) + if v, ok := d.GetOk("supported_ip_address_types"); ok && v.(*schema.Set).Len() > 0 { + input.SupportedIpAddressTypes = flex.ExpandStringSet(v.(*schema.Set)) + } + + log.Printf("[DEBUG] Creating EC2 VPC Endpoint Service: %s", input) + output, err := conn.CreateVpcEndpointServiceConfiguration(input) + if err != nil { - return fmt.Errorf("Error creating VPC Endpoint Service configuration: %s", err.Error()) + return fmt.Errorf("creating EC2 VPC Endpoint Service: %w", err) } - d.SetId(aws.StringValue(resp.ServiceConfiguration.ServiceId)) + d.SetId(aws.StringValue(output.ServiceConfiguration.ServiceId)) - if err := vpcEndpointServiceWaitUntilAvailable(d, conn); err != nil { - return err + if _, err := WaitVPCEndpointServiceAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("waiting for EC2 VPC Endpoint Service (%s) create: %w", d.Id(), err) } if v, ok := d.GetOk("allowed_principals"); ok && v.(*schema.Set).Len() > 0 { - modifyPermReq := &ec2.ModifyVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(d.Id()), + input := &ec2.ModifyVpcEndpointServicePermissionsInput{ AddAllowedPrincipals: flex.ExpandStringSet(v.(*schema.Set)), + ServiceId: aws.String(d.Id()), } - log.Printf("[DEBUG] Adding VPC Endpoint Service permissions: %#v", modifyPermReq) - if _, err := conn.ModifyVpcEndpointServicePermissions(modifyPermReq); err != nil { - return fmt.Errorf("error adding VPC Endpoint Service permissions: %s", err.Error()) + + if _, err := conn.ModifyVpcEndpointServicePermissions(input); err != nil { + return fmt.Errorf("modifying EC2 VPC Endpoint Service (%s) permissions: %w", d.Id(), err) } } @@ -185,22 +198,19 @@ func resourceVPCEndpointServiceRead(d *schema.ResourceData, meta interface{}) er defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - svcCfgRaw, state, err := vpcEndpointServiceStateRefresh(conn, d.Id())() - if err != nil && state != ec2.ServiceStateFailed { - return fmt.Errorf("error reading VPC Endpoint Service (%s): %s", d.Id(), err.Error()) - } + svcCfg, err := FindVPCEndpointServiceConfigurationByID(conn, d.Id()) - terminalStates := map[string]bool{ - ec2.ServiceStateDeleted: true, - ec2.ServiceStateDeleting: true, - ec2.ServiceStateFailed: true, - } - if _, ok := terminalStates[state]; ok { - log.Printf("[WARN] VPC Endpoint Service (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 VPC Endpoint Service %s not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { + return fmt.Errorf("reading EC2 VPC Endpoint Service (%s): %w", d.Id(), err) + } + + d.Set("acceptance_required", svcCfg.AcceptanceRequired) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: ec2.ServiceName, @@ -209,137 +219,104 @@ func resourceVPCEndpointServiceRead(d *schema.ResourceData, meta interface{}) er Resource: fmt.Sprintf("vpc-endpoint-service/%s", d.Id()), }.String() d.Set("arn", arn) - - svcCfg := svcCfgRaw.(*ec2.ServiceConfiguration) - d.Set("acceptance_required", svcCfg.AcceptanceRequired) - err = d.Set("availability_zones", flex.FlattenStringSet(svcCfg.AvailabilityZones)) - if err != nil { - return fmt.Errorf("error setting availability_zones: %s", err) - } - err = d.Set("base_endpoint_dns_names", flex.FlattenStringSet(svcCfg.BaseEndpointDnsNames)) - if err != nil { - return fmt.Errorf("error setting base_endpoint_dns_names: %s", err) - } - - if err := d.Set("gateway_load_balancer_arns", flex.FlattenStringSet(svcCfg.GatewayLoadBalancerArns)); err != nil { - return fmt.Errorf("error setting gateway_load_balancer_arns: %w", err) - } - + d.Set("availability_zones", aws.StringValueSlice(svcCfg.AvailabilityZones)) + d.Set("base_endpoint_dns_names", aws.StringValueSlice(svcCfg.BaseEndpointDnsNames)) + d.Set("gateway_load_balancer_arns", aws.StringValueSlice(svcCfg.GatewayLoadBalancerArns)) d.Set("manages_vpc_endpoints", svcCfg.ManagesVpcEndpoints) - - if err := d.Set("network_load_balancer_arns", flex.FlattenStringSet(svcCfg.NetworkLoadBalancerArns)); err != nil { - return fmt.Errorf("error setting network_load_balancer_arns: %w", err) - } - + d.Set("network_load_balancer_arns", aws.StringValueSlice(svcCfg.NetworkLoadBalancerArns)) d.Set("private_dns_name", svcCfg.PrivateDnsName) + // The EC2 API can return a XML structure with no elements. + if tfMap := flattenPrivateDNSNameConfiguration(svcCfg.PrivateDnsNameConfiguration); len(tfMap) > 0 { + if err := d.Set("private_dns_name_configuration", []interface{}{tfMap}); err != nil { + return fmt.Errorf("setting private_dns_name_configuration: %w", err) + } + } else { + d.Set("private_dns_name_configuration", nil) + } d.Set("service_name", svcCfg.ServiceName) - d.Set("service_type", svcCfg.ServiceType[0].ServiceType) + if len(svcCfg.ServiceType) > 0 { + d.Set("service_type", svcCfg.ServiceType[0].ServiceType) + } else { + d.Set("service_type", nil) + } d.Set("state", svcCfg.ServiceState) + d.Set("supported_ip_address_types", aws.StringValueSlice(svcCfg.SupportedIpAddressTypes)) tags := KeyValueTags(svcCfg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return fmt.Errorf("setting tags: %w", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + return fmt.Errorf("setting tags_all: %w", err) } - resp, err := conn.DescribeVpcEndpointServicePermissions(&ec2.DescribeVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("error reading VPC Endpoint Service permissions (%s): %s", d.Id(), err.Error()) - } + allowedPrincipals, err := FindVPCEndpointServicePermissionsByID(conn, d.Id()) - err = d.Set("allowed_principals", flattenVPCEndpointServiceAllowedPrincipals(resp.AllowedPrincipals)) if err != nil { - return fmt.Errorf("error setting allowed_principals: %s", err) + return fmt.Errorf("reading EC2 VPC Endpoint Service (%s) permissions: %w", d.Id(), err) } - err = d.Set("private_dns_name_configuration", flattenPrivateDNSNameConfiguration(svcCfg.PrivateDnsNameConfiguration)) - if err != nil { - return fmt.Errorf("error setting private_dns_name_configuration: %w", err) - } + d.Set("allowed_principals", flattenAllowedPrincipals(allowedPrincipals)) return nil } -func flattenPrivateDNSNameConfiguration(privateDnsNameConfiguration *ec2.PrivateDnsNameConfiguration) []interface{} { - if privateDnsNameConfiguration == nil { - return nil - } - tfMap := map[string]interface{}{} - - if v := privateDnsNameConfiguration.Name; v != nil { - tfMap["name"] = aws.StringValue(v) - } - - if v := privateDnsNameConfiguration.State; v != nil { - tfMap["state"] = aws.StringValue(v) - } - - if v := privateDnsNameConfiguration.Type; v != nil { - tfMap["type"] = aws.StringValue(v) - } - - if v := privateDnsNameConfiguration.Value; v != nil { - tfMap["value"] = aws.StringValue(v) - } - - // The EC2 API can return a XML structure with no elements - if len(tfMap) == 0 { - return nil - } - - return []interface{}{tfMap} -} - func resourceVPCEndpointServiceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - if d.HasChanges("acceptance_required", "gateway_load_balancer_arns", "network_load_balancer_arns", "private_dns_name") { - modifyCfgReq := &ec2.ModifyVpcEndpointServiceConfigurationInput{ + if d.HasChanges("acceptance_required", "gateway_load_balancer_arns", "network_load_balancer_arns", "private_dns_name", "supported_ip_address_types") { + input := &ec2.ModifyVpcEndpointServiceConfigurationInput{ ServiceId: aws.String(d.Id()), } - if d.HasChange("private_dns_name") { - modifyCfgReq.PrivateDnsName = aws.String(d.Get("private_dns_name").(string)) + if d.HasChange("acceptance_required") { + input.AcceptanceRequired = aws.Bool(d.Get("acceptance_required").(bool)) } - if d.HasChange("acceptance_required") { - modifyCfgReq.AcceptanceRequired = aws.Bool(d.Get("acceptance_required").(bool)) + if d.HasChange("gateway_load_balancer_arns") { + setVPCEndpointServiceUpdateLists(d, "gateway_load_balancer_arns", + &input.AddGatewayLoadBalancerArns, &input.RemoveGatewayLoadBalancerArns) } - setVPCEndpointServiceUpdateLists(d, "gateway_load_balancer_arns", - &modifyCfgReq.AddGatewayLoadBalancerArns, &modifyCfgReq.RemoveGatewayLoadBalancerArns) + if d.HasChange("network_load_balancer_arns") { + setVPCEndpointServiceUpdateLists(d, "network_load_balancer_arns", + &input.AddNetworkLoadBalancerArns, &input.RemoveNetworkLoadBalancerArns) + } - setVPCEndpointServiceUpdateLists(d, "network_load_balancer_arns", - &modifyCfgReq.AddNetworkLoadBalancerArns, &modifyCfgReq.RemoveNetworkLoadBalancerArns) + if d.HasChange("private_dns_name") { + input.PrivateDnsName = aws.String(d.Get("private_dns_name").(string)) + } - log.Printf("[DEBUG] Modifying VPC Endpoint Service configuration: %#v", modifyCfgReq) - if _, err := conn.ModifyVpcEndpointServiceConfiguration(modifyCfgReq); err != nil { - return fmt.Errorf("Error modifying VPC Endpoint Service configuration: %s", err.Error()) + if d.HasChange("supported_ip_address_types") { + setVPCEndpointServiceUpdateLists(d, "supported_ip_address_types", + &input.AddSupportedIpAddressTypes, &input.RemoveSupportedIpAddressTypes) } - if err := vpcEndpointServiceWaitUntilAvailable(d, conn); err != nil { - return err + log.Printf("[DEBUG] Updating EC2 VPC Endpoint Service: %s", input) + _, err := conn.ModifyVpcEndpointServiceConfiguration(input) + + if err != nil { + return fmt.Errorf("updating EC2 VPC Endpoint Service (%s): %w", d.Id(), err) + } + + if _, err := WaitVPCEndpointServiceAvailable(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("waiting for EC2 VPC Endpoint Service (%s) update: %w", d.Id(), err) } } if d.HasChange("allowed_principals") { - modifyPermReq := &ec2.ModifyVpcEndpointServicePermissionsInput{ + input := &ec2.ModifyVpcEndpointServicePermissionsInput{ ServiceId: aws.String(d.Id()), } setVPCEndpointServiceUpdateLists(d, "allowed_principals", - &modifyPermReq.AddAllowedPrincipals, &modifyPermReq.RemoveAllowedPrincipals) + &input.AddAllowedPrincipals, &input.RemoveAllowedPrincipals) - log.Printf("[DEBUG] Modifying VPC Endpoint Service permissions: %#v", modifyPermReq) - if _, err := conn.ModifyVpcEndpointServicePermissions(modifyPermReq); err != nil { - return fmt.Errorf("Error modifying VPC Endpoint Service permissions: %s", err.Error()) + if _, err := conn.ModifyVpcEndpointServicePermissions(input); err != nil { + return fmt.Errorf("modifying EC2 VPC Endpoint Service (%s) permissions: %w", d.Id(), err) } } @@ -347,7 +324,7 @@ func resourceVPCEndpointServiceUpdate(d *schema.ResourceData, meta interface{}) o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating EC2 VPC Endpoint Service (%s) tags: %s", d.Id(), err) + return fmt.Errorf("updating EC2 VPC Endpoint Service (%s) tags: %s", d.Id(), err) } } @@ -357,116 +334,94 @@ func resourceVPCEndpointServiceUpdate(d *schema.ResourceData, meta interface{}) func resourceVPCEndpointServiceDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - input := &ec2.DeleteVpcEndpointServiceConfigurationsInput{ + log.Printf("[INFO] Deleting EC2 VPC Endpoint Service: %s", d.Id()) + output, err := conn.DeleteVpcEndpointServiceConfigurations(&ec2.DeleteVpcEndpointServiceConfigurationsInput{ ServiceIds: aws.StringSlice([]string{d.Id()}), - } + }) - output, err := conn.DeleteVpcEndpointServiceConfigurations(input) + if err == nil && output != nil { + err = UnsuccessfulItemsError(output.Unsuccessful) + } - if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { return nil } if err != nil { - return fmt.Errorf("error deleting EC2 VPC Endpoint Service (%s): %w", d.Id(), err) + return fmt.Errorf("deleting EC2 VPC Endpoint Service (%s): %w", d.Id(), err) } - if output != nil && len(output.Unsuccessful) > 0 { - err := UnsuccessfulItemsError(output.Unsuccessful) - - if err != nil { - return fmt.Errorf("error deleting EC2 VPC Endpoint Service (%s): %w", d.Id(), err) - } - } - - if err := waitForVPCEndpointServiceDeletion(conn, d.Id()); err != nil { - return fmt.Errorf("error waiting for EC2 VPC Endpoint Service (%s) to delete: %w", d.Id(), err) + if _, err := WaitVPCEndpointServiceDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("waiting for EC2 VPC Endpoint Service (%s) delete: %w", d.Id(), err) } return nil } -func vpcEndpointServiceStateRefresh(conn *ec2.EC2, svcId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Printf("[DEBUG] Reading VPC Endpoint Service Configuration: %s", svcId) - resp, err := conn.DescribeVpcEndpointServiceConfigurations(&ec2.DescribeVpcEndpointServiceConfigurationsInput{ - ServiceIds: aws.StringSlice([]string{svcId}), - }) - if err != nil { - if tfawserr.ErrCodeEquals(err, "InvalidVpcEndpointServiceId.NotFound") { - return false, ec2.ServiceStateDeleted, nil - } +func setVPCEndpointServiceUpdateLists(d *schema.ResourceData, key string, a, r *[]*string) { + o, n := d.GetChange(key) + os := o.(*schema.Set) + ns := n.(*schema.Set) - return nil, "", err - } + add := flex.ExpandStringSet(ns.Difference(os)) + if len(add) > 0 { + *a = add + } - svcCfg := resp.ServiceConfigurations[0] - state := aws.StringValue(svcCfg.ServiceState) - // No use in retrying if the endpoint service is in a failed state. - if state == ec2.ServiceStateFailed { - return nil, state, errors.New("VPC Endpoint Service is in a failed state") - } - return svcCfg, state, nil + remove := flex.ExpandStringSet(os.Difference(ns)) + if len(remove) > 0 { + *r = remove } } -func vpcEndpointServiceWaitUntilAvailable(d *schema.ResourceData, conn *ec2.EC2) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.ServiceStatePending}, - Target: []string{ec2.ServiceStateAvailable}, - Refresh: vpcEndpointServiceStateRefresh(conn, d.Id()), - Timeout: 10 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 5 * time.Second, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for VPC Endpoint Service %s to become available: %s", d.Id(), err.Error()) +func flattenAllowedPrincipal(apiObject *ec2.AllowedPrincipal) *string { + if apiObject == nil { + return nil } - return nil + return apiObject.Principal } -func waitForVPCEndpointServiceDeletion(conn *ec2.EC2, serviceID string) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{ec2.ServiceStateAvailable, ec2.ServiceStateDeleting}, - Target: []string{ec2.ServiceStateDeleted}, - Refresh: vpcEndpointServiceStateRefresh(conn, serviceID), - Timeout: 10 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 5 * time.Second, +func flattenAllowedPrincipals(apiObjects []*ec2.AllowedPrincipal) []*string { + if len(apiObjects) == 0 { + return nil } - _, err := stateConf.WaitForState() - - return err -} + var tfList []*string -func setVPCEndpointServiceUpdateLists(d *schema.ResourceData, key string, a, r *[]*string) { - if d.HasChange(key) { - o, n := d.GetChange(key) - os := o.(*schema.Set) - ns := n.(*schema.Set) - - add := flex.ExpandStringSet(ns.Difference(os)) - if len(add) > 0 { - *a = add + for _, apiObject := range apiObjects { + if apiObject == nil { + continue } - remove := flex.ExpandStringSet(os.Difference(ns)) - if len(remove) > 0 { - *r = remove - } + tfList = append(tfList, flattenAllowedPrincipal(apiObject)) } + + return tfList } -func flattenVPCEndpointServiceAllowedPrincipals(allowedPrincipals []*ec2.AllowedPrincipal) *schema.Set { - vPrincipals := []interface{}{} +func flattenPrivateDNSNameConfiguration(apiObject *ec2.PrivateDnsNameConfiguration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} - for _, allowedPrincipal := range allowedPrincipals { - if allowedPrincipal.Principal != nil { - vPrincipals = append(vPrincipals, aws.StringValue(allowedPrincipal.Principal)) - } + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.State; v != nil { + tfMap["state"] = aws.StringValue(v) + } + + if v := apiObject.Type; v != nil { + tfMap["type"] = aws.StringValue(v) + } + + if v := apiObject.Value; v != nil { + tfMap["value"] = aws.StringValue(v) } - return schema.NewSet(schema.HashString, vPrincipals) + return tfMap } diff --git a/internal/service/ec2/vpc_endpoint_service_allowed_principal.go b/internal/service/ec2/vpc_endpoint_service_allowed_principal.go index f82f4bf1bea5..4de0daa78210 100644 --- a/internal/service/ec2/vpc_endpoint_service_allowed_principal.go +++ b/internal/service/ec2/vpc_endpoint_service_allowed_principal.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceVPCEndpointServiceAllowedPrincipal() *schema.Resource { @@ -19,12 +20,12 @@ func ResourceVPCEndpointServiceAllowedPrincipal() *schema.Resource { Delete: resourceVPCEndpointServiceAllowedPrincipalDelete, Schema: map[string]*schema.Schema{ - "vpc_endpoint_service_id": { + "principal_arn": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "principal_arn": { + "vpc_endpoint_service_id": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -36,23 +37,19 @@ func ResourceVPCEndpointServiceAllowedPrincipal() *schema.Resource { func resourceVPCEndpointServiceAllowedPrincipalCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - svcId := d.Get("vpc_endpoint_service_id").(string) - arn := d.Get("principal_arn").(string) - - _, err := findResourceVPCEndpointServiceAllowedPrincipals(conn, svcId) - if err != nil { - return err - } + serviceID := d.Get("vpc_endpoint_service_id").(string) + principalARN := d.Get("principal_arn").(string) - _, err = conn.ModifyVpcEndpointServicePermissions(&ec2.ModifyVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(svcId), - AddAllowedPrincipals: aws.StringSlice([]string{arn}), + _, err := conn.ModifyVpcEndpointServicePermissions(&ec2.ModifyVpcEndpointServicePermissionsInput{ + AddAllowedPrincipals: aws.StringSlice([]string{principalARN}), + ServiceId: aws.String(serviceID), }) + if err != nil { - return fmt.Errorf("Error creating VPC Endpoint Service allowed principal: %s", err.Error()) + return fmt.Errorf("modifying EC2 VPC Endpoint Service (%s) permissions: %w", serviceID, err) } - d.SetId(vpcEndpointServiceIdPrincipalARNHash(svcId, arn)) + d.SetId(fmt.Sprintf("a-%s%d", serviceID, create.StringHashcode(principalARN))) return resourceVPCEndpointServiceAllowedPrincipalRead(d, meta) } @@ -60,66 +57,42 @@ func resourceVPCEndpointServiceAllowedPrincipalCreate(d *schema.ResourceData, me func resourceVPCEndpointServiceAllowedPrincipalRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - svcId := d.Get("vpc_endpoint_service_id").(string) - arn := d.Get("principal_arn").(string) - - principals, err := findResourceVPCEndpointServiceAllowedPrincipals(conn, svcId) - if err != nil { - if tfawserr.ErrCodeEquals(err, "InvalidVpcEndpointServiceId.NotFound") { - log.Printf("[WARN]VPC Endpoint Service (%s) not found, removing VPC Endpoint Service allowed principal (%s) from state", svcId, d.Id()) - d.SetId("") - return nil - } + serviceID := d.Get("vpc_endpoint_service_id").(string) + principalARN := d.Get("principal_arn").(string) - return err - } + err := FindVPCEndpointServicePermissionExists(conn, serviceID, principalARN) - found := false - for _, principal := range principals { - if aws.StringValue(principal.Principal) == arn { - found = true - break - } - } - if !found { - log.Printf("[WARN] VPC Endpoint Service allowed principal (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 VPC Endpoint Service Allowed Principal %s not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { + return fmt.Errorf("reading EC2 VPC Endpoint Service (%s) Allowed Principal (%s): %w", serviceID, principalARN, err) + } + return nil } func resourceVPCEndpointServiceAllowedPrincipalDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - svcId := d.Get("vpc_endpoint_service_id").(string) - arn := d.Get("principal_arn").(string) + serviceID := d.Get("vpc_endpoint_service_id").(string) + principalARN := d.Get("principal_arn").(string) _, err := conn.ModifyVpcEndpointServicePermissions(&ec2.ModifyVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(svcId), - RemoveAllowedPrincipals: aws.StringSlice([]string{arn}), + RemoveAllowedPrincipals: aws.StringSlice([]string{principalARN}), + ServiceId: aws.String(serviceID), }) - if err != nil { - if !tfawserr.ErrCodeEquals(err, "InvalidVpcEndpointServiceId.NotFound") { - return fmt.Errorf("Error deleting VPC Endpoint Service allowed principal: %s", err.Error()) - } - } - return nil -} + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { + return nil + } -func findResourceVPCEndpointServiceAllowedPrincipals(conn *ec2.EC2, id string) ([]*ec2.AllowedPrincipal, error) { - resp, err := conn.DescribeVpcEndpointServicePermissions(&ec2.DescribeVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(id), - }) if err != nil { - return nil, err + return fmt.Errorf("modifying EC2 VPC Endpoint Service (%s) permissions: %w", serviceID, err) } - return resp.AllowedPrincipals, nil -} - -func vpcEndpointServiceIdPrincipalARNHash(svcId, arn string) string { - return fmt.Sprintf("a-%s%d", svcId, create.StringHashcode(arn)) + return nil } diff --git a/internal/service/ec2/vpc_endpoint_service_allowed_principal_test.go b/internal/service/ec2/vpc_endpoint_service_allowed_principal_test.go index a6934db58f74..3b26c860b0d3 100644 --- a/internal/service/ec2/vpc_endpoint_service_allowed_principal_test.go +++ b/internal/service/ec2/vpc_endpoint_service_allowed_principal_test.go @@ -4,18 +4,19 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccVPCEndpointServiceAllowedPrincipal_basic(t *testing.T) { - lbName := fmt.Sprintf("testAccNLB-basic-%s", sdkacctest.RandString(10)) + resourceName := "aws_vpc_endpoint_service_allowed_principal.test" + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -24,9 +25,9 @@ func TestAccVPCEndpointServiceAllowedPrincipal_basic(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointServiceAllowedPrincipalDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceAllowedPrincipalConfig_basic(lbName), + Config: testAccVPCEndpointServiceAllowedPrincipalConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckVPCEndpointServiceAllowedPrincipalExists("aws_vpc_endpoint_service_allowed_principal.foo"), + testAccCheckVPCEndpointServiceAllowedPrincipalExists(resourceName), ), }, }, @@ -41,26 +42,17 @@ func testAccCheckVPCEndpointServiceAllowedPrincipalDestroy(s *terraform.State) e continue } - // Try to find the resource - resp, err := conn.DescribeVpcEndpointServicePermissions(&ec2.DescribeVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(rs.Primary.Attributes["vpc_endpoint_service_id"]), - }) - if err != nil { - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { - return err - } - if ec2err.Code() != "InvalidVpcEndpointServiceId.NotFound" { - return err - } - return nil + err := tfec2.FindVPCEndpointServicePermissionExists(conn, rs.Primary.Attributes["vpc_endpoint_service_id"], rs.Primary.Attributes["principal_arn"]) + + if tfresource.NotFound(err) { + continue } - if len(resp.AllowedPrincipals) > 0 { - return fmt.Errorf( - "VCP Endpoint Service %s has allowed principals", rs.Primary.Attributes["vpc_endpoint_service_id"]) + if err != nil { + return err } + + return fmt.Errorf("EC2 VPC Endpoint Service Allowed Principal %s still exists", rs.Primary.ID) } return nil @@ -74,91 +66,36 @@ func testAccCheckVPCEndpointServiceAllowedPrincipalExists(n string) resource.Tes } if rs.Primary.ID == "" { - return fmt.Errorf("No VPC Endpoint Service ID is set") + return fmt.Errorf("No EC2 VPC Endpoint Service Allowed Principal ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - resp, err := conn.DescribeVpcEndpointServicePermissions(&ec2.DescribeVpcEndpointServicePermissionsInput{ - ServiceId: aws.String(rs.Primary.Attributes["vpc_endpoint_service_id"]), - }) - if err != nil { - return err - } - - for _, principal := range resp.AllowedPrincipals { - if aws.StringValue(principal.Principal) == rs.Primary.Attributes["principal_arn"] { - return nil - } - } - - return fmt.Errorf("VPC Endpoint Service allowed principal not found") + return tfec2.FindVPCEndpointServicePermissionExists(conn, rs.Primary.Attributes["vpc_endpoint_service_id"], rs.Primary.Attributes["principal_arn"]) } } -func testAccVPCEndpointServiceAllowedPrincipalConfig_basic(lbName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf( - ` -resource "aws_vpc" "nlb_test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = "terraform-testacc-vpc-endpoint-service-allowed-principal" - } -} - -resource "aws_lb" "nlb_test_1" { - name = "%s" - - subnets = [ - aws_subnet.nlb_test_1.id, - aws_subnet.nlb_test_2.id, - ] - - load_balancer_type = "network" - internal = true - idle_timeout = 60 - enable_deletion_protection = false - - tags = { - Name = "testAccVpcEndpointServiceBasicConfig_nlb1" - } -} - -resource "aws_subnet" "nlb_test_1" { - vpc_id = aws_vpc.nlb_test.id - cidr_block = "10.0.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] +func testAccVPCEndpointServiceAllowedPrincipalConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), fmt.Sprintf(` +data "aws_caller_identity" "current" {} - tags = { - Name = "tf-acc-vpc-endpoint-service-allowed-principal-1" - } +data "aws_iam_session_context" "current" { + arn = data.aws_caller_identity.current.arn } -resource "aws_subnet" "nlb_test_2" { - vpc_id = aws_vpc.nlb_test.id - cidr_block = "10.0.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] +resource "aws_vpc_endpoint_service" "test" { + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn tags = { - Name = "tf-acc-vpc-endpoint-service-allowed-principal-2" + Name = %[1]q } } -data "aws_caller_identity" "current" {} - -resource "aws_vpc_endpoint_service" "foo" { - acceptance_required = false - - network_load_balancer_arns = [ - aws_lb.nlb_test_1.id, - ] -} - -resource "aws_vpc_endpoint_service_allowed_principal" "foo" { - vpc_endpoint_service_id = aws_vpc_endpoint_service.foo.id +resource "aws_vpc_endpoint_service_allowed_principal" "test" { + vpc_endpoint_service_id = aws_vpc_endpoint_service.test.id - principal_arn = data.aws_caller_identity.current.arn + principal_arn = data.aws_iam_session_context.current.issuer_arn } -`, lbName)) +`, rName)) } diff --git a/internal/service/ec2/vpc_endpoint_service_data_source.go b/internal/service/ec2/vpc_endpoint_service_data_source.go index 1f29b50cfa69..c331c86682b2 100644 --- a/internal/service/ec2/vpc_endpoint_service_data_source.go +++ b/internal/service/ec2/vpc_endpoint_service_data_source.go @@ -2,7 +2,6 @@ package ec2 import ( "fmt" - "log" "strconv" "github.com/aws/aws-sdk-go/aws" @@ -12,7 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" ) @@ -33,14 +31,13 @@ func DataSourceVPCEndpointService() *schema.Resource { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "base_endpoint_dns_names": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, + "filter": CustomFiltersSchema(), "manages_vpc_endpoints": { Type: schema.TypeBool, Computed: true, @@ -74,12 +71,16 @@ func DataSourceVPCEndpointService() *schema.Resource { Computed: true, ValidateFunc: validation.StringInSlice(ec2.ServiceType_Values(), false), }, + "supported_ip_address_types": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, "tags": tftags.TagsSchemaComputed(), "vpc_endpoint_policy_supported": { Type: schema.TypeBool, Computed: true, }, - "filter": DataSourceFiltersSchema(), }, } } @@ -88,54 +89,56 @@ func dataSourceVPCEndpointServiceRead(d *schema.ResourceData, meta interface{}) conn := meta.(*conns.AWSClient).EC2Conn ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - filters, filtersOk := d.GetOk("filter") - tags, tagsOk := d.GetOk("tags") + input := &ec2.DescribeVpcEndpointServicesInput{ + Filters: BuildAttributeFilterList( + map[string]string{ + "service-type": d.Get("service_type").(string), + }, + ), + } var serviceName string - serviceNameOk := false + if v, ok := d.GetOk("service_name"); ok { serviceName = v.(string) - serviceNameOk = true } else if v, ok := d.GetOk("service"); ok { serviceName = fmt.Sprintf("com.amazonaws.%s.%s", meta.(*conns.AWSClient).Region, v.(string)) - serviceNameOk = true } - req := &ec2.DescribeVpcEndpointServicesInput{} - if filtersOk { - req.Filters = BuildFiltersDataSource(filters.(*schema.Set)) - } - if serviceNameOk { - req.ServiceNames = aws.StringSlice([]string{serviceName}) + if serviceName != "" { + input.ServiceNames = aws.StringSlice([]string{serviceName}) } - if v, ok := d.GetOk("service_type"); ok { - req.Filters = append(req.Filters, &ec2.Filter{ - Name: aws.String("service-type"), - Values: aws.StringSlice([]string{v.(string)}), - }) + if v, ok := d.GetOk("tags"); ok { + input.Filters = append(input.Filters, BuildTagFilterList( + Tags(tftags.New(v.(map[string]interface{}))), + )...) } - if tagsOk { - req.Filters = append(req.Filters, tagFiltersFromMap(tags.(map[string]interface{}))...) + input.Filters = append(input.Filters, BuildCustomFilterList( + d.Get("filter").(*schema.Set), + )...) + + if len(input.Filters) == 0 { + // Don't send an empty filters list; the EC2 API won't accept it. + input.Filters = nil } - log.Printf("[DEBUG] Reading VPC Endpoint Service: %s", req) - resp, err := conn.DescribeVpcEndpointServices(req) + serviceDetails, serviceNames, err := FindVPCEndpointServices(conn, input) + if err != nil { - return fmt.Errorf("error reading VPC Endpoint Service (%s): %w", serviceName, err) + return fmt.Errorf("reading EC2 VPC Endpoint Services: %w", err) } - if resp == nil || (len(resp.ServiceNames) == 0 && len(resp.ServiceDetails) == 0) { - return fmt.Errorf("no matching VPC Endpoint Service found") + if len(serviceDetails) == 0 && len(serviceNames) == 0 { + return fmt.Errorf("no matching EC2 VPC Endpoint Service found") } // Note: AWS Commercial now returns a response with `ServiceNames` and // `ServiceDetails`, but GovCloud responses only include `ServiceNames` - if len(resp.ServiceDetails) == 0 { + if len(serviceDetails) == 0 { // GovCloud doesn't respect the filter. - names := aws.StringValueSlice(resp.ServiceNames) - for _, name := range names { + for _, name := range serviceNames { if name == serviceName { d.SetId(strconv.Itoa(create.StringHashcode(name))) d.Set("service_name", name) @@ -143,48 +146,49 @@ func dataSourceVPCEndpointServiceRead(d *schema.ResourceData, meta interface{}) } } - return fmt.Errorf("no matching VPC Endpoint Service found") + return fmt.Errorf("no matching EC2 VPC Endpoint Service found") } - if len(resp.ServiceDetails) > 1 { - return fmt.Errorf("multiple VPC Endpoint Services matched; use additional constraints to reduce matches to a single VPC Endpoint Service") + if len(serviceDetails) > 1 { + return fmt.Errorf("multiple EC2 VPC Endpoint Services matched; use additional constraints to reduce matches to a single EC2 VPC Endpoint Service") } - sd := resp.ServiceDetails[0] - serviceId := aws.StringValue(sd.ServiceId) + sd := serviceDetails[0] + serviceID := aws.StringValue(sd.ServiceId) serviceName = aws.StringValue(sd.ServiceName) d.SetId(strconv.Itoa(create.StringHashcode(serviceName))) + d.Set("acceptance_required", sd.AcceptanceRequired) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: ec2.ServiceName, Region: meta.(*conns.AWSClient).Region, AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("vpc-endpoint-service/%s", serviceId), + Resource: fmt.Sprintf("vpc-endpoint-service/%s", serviceID), }.String() d.Set("arn", arn) - d.Set("acceptance_required", sd.AcceptanceRequired) - err = d.Set("availability_zones", flex.FlattenStringSet(sd.AvailabilityZones)) - if err != nil { - return fmt.Errorf("error setting availability_zones: %w", err) - } - err = d.Set("base_endpoint_dns_names", flex.FlattenStringSet(sd.BaseEndpointDnsNames)) - if err != nil { - return fmt.Errorf("error setting base_endpoint_dns_names: %w", err) - } + d.Set("availability_zones", aws.StringValueSlice(sd.AvailabilityZones)) + d.Set("base_endpoint_dns_names", aws.StringValueSlice(sd.BaseEndpointDnsNames)) d.Set("manages_vpc_endpoints", sd.ManagesVpcEndpoints) d.Set("owner", sd.Owner) d.Set("private_dns_name", sd.PrivateDnsName) - d.Set("service_id", serviceId) + d.Set("service_id", serviceID) d.Set("service_name", serviceName) - d.Set("service_type", sd.ServiceType[0].ServiceType) + if len(sd.ServiceType) > 0 { + d.Set("service_type", sd.ServiceType[0].ServiceType) + } else { + d.Set("service_type", nil) + } + d.Set("supported_ip_address_types", aws.StringValueSlice(sd.SupportedIpAddressTypes)) + d.Set("vpc_endpoint_policy_supported", sd.VpcEndpointPolicySupported) + err = d.Set("tags", KeyValueTags(sd.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()) + if err != nil { - return fmt.Errorf("error setting tags: %w", err) + return fmt.Errorf("setting tags: %w", err) } - d.Set("vpc_endpoint_policy_supported", sd.VpcEndpointPolicySupported) return nil } diff --git a/internal/service/ec2/vpc_endpoint_service_data_source_test.go b/internal/service/ec2/vpc_endpoint_service_data_source_test.go index 269248e7df93..a284420f426a 100644 --- a/internal/service/ec2/vpc_endpoint_service_data_source_test.go +++ b/internal/service/ec2/vpc_endpoint_service_data_source_test.go @@ -23,17 +23,18 @@ func TestAccVPCEndpointServiceDataSource_gateway(t *testing.T) { { Config: testAccVPCEndpointServiceDataSourceConfig_gateway, Check: resource.ComposeTestCheckFunc( - testAccCheckResourceAttrRegionalReverseDNSService(datasourceName, "service_name", "dynamodb"), resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "false"), - resource.TestCheckResourceAttrPair(datasourceName, "availability_zones.#", "data.aws_availability_zones.available", "names.#"), + acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + acctest.CheckResourceAttrGreaterThanValue(datasourceName, "availability_zones.#", "0"), resource.TestCheckResourceAttr(datasourceName, "base_endpoint_dns_names.#", "1"), resource.TestCheckResourceAttr(datasourceName, "manages_vpc_endpoints", "false"), resource.TestCheckResourceAttr(datasourceName, "owner", "amazon"), resource.TestCheckResourceAttr(datasourceName, "private_dns_name", ""), + testAccCheckResourceAttrRegionalReverseDNSService(datasourceName, "service_name", "dynamodb"), resource.TestCheckResourceAttr(datasourceName, "service_type", "Gateway"), - resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "true"), + acctest.CheckResourceAttrGreaterThanValue(datasourceName, "supported_ip_address_types.#", "0"), resource.TestCheckResourceAttr(datasourceName, "tags.%", "0"), - acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "true"), ), }, }, @@ -51,16 +52,18 @@ func TestAccVPCEndpointServiceDataSource_interface(t *testing.T) { { Config: testAccVPCEndpointServiceDataSourceConfig_interface, Check: resource.ComposeTestCheckFunc( - testAccCheckResourceAttrRegionalReverseDNSService(datasourceName, "service_name", "ec2"), resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "false"), + acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + acctest.CheckResourceAttrGreaterThanValue(datasourceName, "availability_zones.#", "0"), resource.TestCheckResourceAttr(datasourceName, "base_endpoint_dns_names.#", "1"), resource.TestCheckResourceAttr(datasourceName, "manages_vpc_endpoints", "false"), resource.TestCheckResourceAttr(datasourceName, "owner", "amazon"), acctest.CheckResourceAttrRegionalHostnameService(datasourceName, "private_dns_name", "ec2"), + testAccCheckResourceAttrRegionalReverseDNSService(datasourceName, "service_name", "ec2"), resource.TestCheckResourceAttr(datasourceName, "service_type", "Interface"), - resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "true"), + acctest.CheckResourceAttrGreaterThanValue(datasourceName, "supported_ip_address_types.#", "0"), resource.TestCheckResourceAttr(datasourceName, "tags.%", "0"), - acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "true"), ), }, }, @@ -68,8 +71,9 @@ func TestAccVPCEndpointServiceDataSource_interface(t *testing.T) { } func TestAccVPCEndpointServiceDataSource_custom(t *testing.T) { + resourceName := "aws_vpc_endpoint_service.test" datasourceName := "data.aws_vpc_endpoint_service.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -78,16 +82,18 @@ func TestAccVPCEndpointServiceDataSource_custom(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccVPCEndpointServiceDataSourceConfig_custom(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "true"), - resource.TestCheckResourceAttr(datasourceName, "availability_zones.#", "2"), - resource.TestCheckResourceAttr(datasourceName, "manages_vpc_endpoints", "false"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "acceptance_required", resourceName, "acceptance_required"), + resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(datasourceName, "availability_zones.#", resourceName, "availability_zones.#"), + resource.TestCheckResourceAttrPair(datasourceName, "base_endpoint_dns_names.#", resourceName, "base_endpoint_dns_names.#"), + resource.TestCheckResourceAttrPair(datasourceName, "manages_vpc_endpoints", resourceName, "manages_vpc_endpoints"), acctest.CheckResourceAttrAccountID(datasourceName, "owner"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name", resourceName, "private_dns_name"), resource.TestCheckResourceAttr(datasourceName, "service_type", "Interface"), + resource.TestCheckResourceAttrPair(datasourceName, "supported_ip_address_types.#", resourceName, "supported_ip_address_types.#"), + resource.TestCheckResourceAttrPair(datasourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "false"), - resource.TestCheckResourceAttr(datasourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(datasourceName, "tags.Name", rName), - acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), ), }, }, @@ -95,8 +101,9 @@ func TestAccVPCEndpointServiceDataSource_custom(t *testing.T) { } func TestAccVPCEndpointServiceDataSource_Custom_filter(t *testing.T) { + resourceName := "aws_vpc_endpoint_service.test" datasourceName := "data.aws_vpc_endpoint_service.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -105,16 +112,18 @@ func TestAccVPCEndpointServiceDataSource_Custom_filter(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccVPCEndpointServiceDataSourceConfig_customFilter(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "true"), - resource.TestCheckResourceAttr(datasourceName, "availability_zones.#", "2"), - resource.TestCheckResourceAttr(datasourceName, "manages_vpc_endpoints", "false"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "acceptance_required", resourceName, "acceptance_required"), + resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(datasourceName, "availability_zones.#", resourceName, "availability_zones.#"), + resource.TestCheckResourceAttrPair(datasourceName, "base_endpoint_dns_names.#", resourceName, "base_endpoint_dns_names.#"), + resource.TestCheckResourceAttrPair(datasourceName, "manages_vpc_endpoints", resourceName, "manages_vpc_endpoints"), acctest.CheckResourceAttrAccountID(datasourceName, "owner"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name", resourceName, "private_dns_name"), resource.TestCheckResourceAttr(datasourceName, "service_type", "Interface"), + resource.TestCheckResourceAttrPair(datasourceName, "supported_ip_address_types.#", resourceName, "supported_ip_address_types.#"), + resource.TestCheckResourceAttrPair(datasourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "false"), - resource.TestCheckResourceAttr(datasourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(datasourceName, "tags.Name", rName), - acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), ), }, }, @@ -122,8 +131,9 @@ func TestAccVPCEndpointServiceDataSource_Custom_filter(t *testing.T) { } func TestAccVPCEndpointServiceDataSource_CustomFilter_tags(t *testing.T) { + resourceName := "aws_vpc_endpoint_service.test" datasourceName := "data.aws_vpc_endpoint_service.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -132,16 +142,18 @@ func TestAccVPCEndpointServiceDataSource_CustomFilter_tags(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccVPCEndpointServiceDataSourceConfig_customFilterTags(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(datasourceName, "acceptance_required", "true"), - resource.TestCheckResourceAttr(datasourceName, "availability_zones.#", "2"), - resource.TestCheckResourceAttr(datasourceName, "manages_vpc_endpoints", "false"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "acceptance_required", resourceName, "acceptance_required"), + resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(datasourceName, "availability_zones.#", resourceName, "availability_zones.#"), + resource.TestCheckResourceAttrPair(datasourceName, "base_endpoint_dns_names.#", resourceName, "base_endpoint_dns_names.#"), + resource.TestCheckResourceAttrPair(datasourceName, "manages_vpc_endpoints", resourceName, "manages_vpc_endpoints"), acctest.CheckResourceAttrAccountID(datasourceName, "owner"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name", resourceName, "private_dns_name"), resource.TestCheckResourceAttr(datasourceName, "service_type", "Interface"), + resource.TestCheckResourceAttrPair(datasourceName, "supported_ip_address_types.#", resourceName, "supported_ip_address_types.#"), + resource.TestCheckResourceAttrPair(datasourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttr(datasourceName, "vpc_endpoint_policy_supported", "false"), - resource.TestCheckResourceAttr(datasourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(datasourceName, "tags.Name", rName), - acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), ), }, }, @@ -198,8 +210,6 @@ func testAccCheckResourceAttrRegionalReverseDNSService(resourceName, attributeNa } const testAccVPCEndpointServiceDataSourceConfig_gateway = ` -data "aws_availability_zones" "available" {} - data "aws_vpc_endpoint_service" "test" { service = "dynamodb" } @@ -220,102 +230,44 @@ data "aws_vpc_endpoint_service" "test" { `, service, serviceType) } -func testAccVPCEndpointServiceCustomBaseDataSourceConfig(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_lb" "test" { - name = %[1]q - - subnets = [ - aws_subnet.test1.id, - aws_subnet.test2.id, - ] - - load_balancer_type = "network" - internal = true - idle_timeout = 60 - enable_deletion_protection = false - - tags = { - Name = %[1]q - } -} - -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_subnet" "test1" { - vpc_id = aws_vpc.test.id - cidr_block = "10.0.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - cidr_block = "10.0.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] - - tags = { - Name = %[1]q - } -} - +func testAccVPCEndpointServiceDataSourceConfig_customBase(rName string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), fmt.Sprintf(` resource "aws_vpc_endpoint_service" "test" { - acceptance_required = true - - network_load_balancer_arns = [ - aws_lb.test.id, - ] + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn tags = { Name = %[1]q } } -`, rName) +`, rName)) } func testAccVPCEndpointServiceDataSourceConfig_custom(rName string) string { - return testAccVPCEndpointServiceCustomBaseDataSourceConfig(rName) + ` + return acctest.ConfigCompose(testAccVPCEndpointServiceDataSourceConfig_customBase(rName), ` data "aws_vpc_endpoint_service" "test" { service_name = aws_vpc_endpoint_service.test.service_name } -` +`) } func testAccVPCEndpointServiceDataSourceConfig_customFilter(rName string) string { - return testAccVPCEndpointServiceCustomBaseDataSourceConfig(rName) + ` + return acctest.ConfigCompose(testAccVPCEndpointServiceDataSourceConfig_customBase(rName), ` data "aws_vpc_endpoint_service" "test" { filter { name = "service-name" values = [aws_vpc_endpoint_service.test.service_name] } } -` +`) } func testAccVPCEndpointServiceDataSourceConfig_customFilterTags(rName string) string { - return testAccVPCEndpointServiceCustomBaseDataSourceConfig(rName) + ` + return acctest.ConfigCompose(testAccVPCEndpointServiceDataSourceConfig_customBase(rName), ` data "aws_vpc_endpoint_service" "test" { tags = { Name = aws_vpc_endpoint_service.test.tags["Name"] } } -` +`) } diff --git a/internal/service/ec2/vpc_endpoint_service_test.go b/internal/service/ec2/vpc_endpoint_service_test.go index ab1066a998df..d509127c98ad 100644 --- a/internal/service/ec2/vpc_endpoint_service_test.go +++ b/internal/service/ec2/vpc_endpoint_service_test.go @@ -5,22 +5,20 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccVPCEndpointService_basic(t *testing.T) { var svcCfg ec2.ServiceConfiguration resourceName := "aws_vpc_endpoint_service.test" - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -29,16 +27,24 @@ func TestAccVPCEndpointService_basic(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointServiceDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceConfig_networkLoadBalancerARNs(rName1, rName2), - Check: resource.ComposeTestCheckFunc( + Config: testAccVPCEndpointServiceConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), resource.TestCheckResourceAttr(resourceName, "acceptance_required", "false"), - resource.TestCheckResourceAttr(resourceName, "network_load_balancer_arns.#", "1"), resource.TestCheckResourceAttr(resourceName, "allowed_principals.#", "0"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + acctest.CheckResourceAttrGreaterThanValue(resourceName, "availability_zones.#", "0"), + acctest.CheckResourceAttrGreaterThanValue(resourceName, "base_endpoint_dns_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "gateway_load_balancer_arns.#", "0"), resource.TestCheckResourceAttr(resourceName, "manages_vpc_endpoints", "false"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "network_load_balancer_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name", ""), resource.TestCheckResourceAttr(resourceName, "private_dns_name_configuration.#", "0"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + resource.TestCheckResourceAttrSet(resourceName, "service_name"), + resource.TestCheckResourceAttr(resourceName, "service_type", "Interface"), + resource.TestCheckResourceAttr(resourceName, "supported_ip_address_types.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "supported_ip_address_types.*", "ipv4"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -50,11 +56,10 @@ func TestAccVPCEndpointService_basic(t *testing.T) { }) } -func TestAccVPCEndpointService_allowedPrincipals(t *testing.T) { +func TestAccVPCEndpointService_disappears(t *testing.T) { var svcCfg ec2.ServiceConfiguration resourceName := "aws_vpc_endpoint_service.test" - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -63,16 +68,34 @@ func TestAccVPCEndpointService_allowedPrincipals(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointServiceDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceConfig_allowedPrincipals(rName1, rName2), + Config: testAccVPCEndpointServiceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPCEndpointService(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccVPCEndpointService_tags(t *testing.T) { + var svcCfg ec2.ServiceConfiguration + resourceName := "aws_vpc_endpoint_service.test" + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckVPCEndpointServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCEndpointServiceConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "acceptance_required", "false"), - resource.TestCheckResourceAttr(resourceName, "network_load_balancer_arns.#", "1"), - resource.TestCheckResourceAttr(resourceName, "allowed_principals.#", "1"), - resource.TestCheckResourceAttr(resourceName, "manages_vpc_endpoints", "false"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName1), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`vpc-endpoint-service/vpce-svc-.+`)), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -81,25 +104,30 @@ func TestAccVPCEndpointService_allowedPrincipals(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVPCEndpointServiceConfig_allowedPrincipalsUpdated(rName1, rName2), + Config: testAccVPCEndpointServiceConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccVPCEndpointServiceConfig_tags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "acceptance_required", "true"), - resource.TestCheckResourceAttr(resourceName, "network_load_balancer_arns.#", "2"), - resource.TestCheckResourceAttr(resourceName, "allowed_principals.#", "0"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName1), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, }, }) } -func TestAccVPCEndpointService_disappears(t *testing.T) { +func TestAccVPCEndpointService_networkLoadBalancerARNs(t *testing.T) { var svcCfg ec2.ServiceConfiguration resourceName := "aws_vpc_endpoint_service.test" - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -108,33 +136,45 @@ func TestAccVPCEndpointService_disappears(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointServiceDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceConfig_networkLoadBalancerARNs(rName1, rName2), + Config: testAccVPCEndpointServiceConfig_networkLoadBalancerARNs(rName, 1), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPCEndpointService(), resourceName), + resource.TestCheckResourceAttr(resourceName, "network_load_balancer_arns.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccVPCEndpointServiceConfig_networkLoadBalancerARNs(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), + resource.TestCheckResourceAttr(resourceName, "network_load_balancer_arns.#", "2"), ), - ExpectNonEmptyPlan: true, }, }, }) } -func TestAccVPCEndpointService_gatewayLoadBalancerARNs(t *testing.T) { +func TestAccVPCEndpointService_supportedIPAddressTypes(t *testing.T) { var svcCfg ec2.ServiceConfiguration resourceName := "aws_vpc_endpoint_service.test" rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckELBv2GatewayLoadBalancer(t) }, + PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, CheckDestroy: testAccCheckVPCEndpointServiceDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceConfig_gatewayLoadBalancerARNs(rName, 1), + Config: testAccVPCEndpointServiceConfig_supportedIPAddressTypesIPv4(rName), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "gateway_load_balancer_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "supported_ip_address_types.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "supported_ip_address_types.*", "ipv4"), ), }, { @@ -143,21 +183,22 @@ func TestAccVPCEndpointService_gatewayLoadBalancerARNs(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVPCEndpointServiceConfig_gatewayLoadBalancerARNs(rName, 2), + Config: testAccVPCEndpointServiceConfig_supportedIPAddressTypesIPv4AndIPv6(rName), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "gateway_load_balancer_arns.#", "2"), + resource.TestCheckResourceAttr(resourceName, "supported_ip_address_types.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "supported_ip_address_types.*", "ipv4"), + resource.TestCheckTypeSetElemAttr(resourceName, "supported_ip_address_types.*", "ipv6"), ), }, }, }) } -func TestAccVPCEndpointService_tags(t *testing.T) { +func TestAccVPCEndpointService_allowedPrincipals(t *testing.T) { var svcCfg ec2.ServiceConfiguration resourceName := "aws_vpc_endpoint_service.test" - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -166,11 +207,10 @@ func TestAccVPCEndpointService_tags(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointServiceDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceConfig_tags1(rName1, rName2, "key1", "value1"), + Config: testAccVPCEndpointServiceConfig_allowedPrincipals(rName, 1), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "allowed_principals.#", "1"), ), }, { @@ -179,31 +219,63 @@ func TestAccVPCEndpointService_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVPCEndpointServiceConfig_tags2(rName1, rName2, "key1", "value1updated", "key2", "value2"), + Config: testAccVPCEndpointServiceConfig_allowedPrincipals(rName, 0), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "allowed_principals.#", "0"), ), }, { - Config: testAccVPCEndpointServiceConfig_tags1(rName1, rName2, "key2", "value2"), + Config: testAccVPCEndpointServiceConfig_allowedPrincipals(rName, 1), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "allowed_principals.#", "1"), ), }, }, }) } -func TestAccVPCEndpointService_PrivateDNS_name(t *testing.T) { +func TestAccVPCEndpointService_gatewayLoadBalancerARNs(t *testing.T) { var svcCfg ec2.ServiceConfiguration resourceName := "aws_vpc_endpoint_service.test" - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckELBv2GatewayLoadBalancer(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckVPCEndpointServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCEndpointServiceConfig_gatewayLoadBalancerARNs(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), + resource.TestCheckResourceAttr(resourceName, "gateway_load_balancer_arns.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccVPCEndpointServiceConfig_gatewayLoadBalancerARNs(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), + resource.TestCheckResourceAttr(resourceName, "gateway_load_balancer_arns.#", "2"), + ), + }, + }, + }) +} + +func TestAccVPCEndpointService_privateDNSName(t *testing.T) { + var svcCfg ec2.ServiceConfiguration + resourceName := "aws_vpc_endpoint_service.test" + rName := sdkacctest.RandomWithPrefix("tfacctest") // 32 character limit + domainName1 := acctest.RandomSubdomain() + domainName2 := acctest.RandomSubdomain() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -212,10 +284,10 @@ func TestAccVPCEndpointService_PrivateDNS_name(t *testing.T) { CheckDestroy: testAccCheckVPCEndpointServiceDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCEndpointServiceConfig_privateDNSName(rName1, rName2, "example.com"), + Config: testAccVPCEndpointServiceConfig_privateDNSName(rName, domainName1), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "private_dns_name", "example.com"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name", domainName1), resource.TestCheckResourceAttr(resourceName, "private_dns_name_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "private_dns_name_configuration.0.type", "TXT"), ), @@ -226,10 +298,10 @@ func TestAccVPCEndpointService_PrivateDNS_name(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVPCEndpointServiceConfig_privateDNSName(rName1, rName2, "changed.example.com"), + Config: testAccVPCEndpointServiceConfig_privateDNSName(rName, domainName2), Check: resource.ComposeTestCheckFunc( testAccCheckVPCEndpointServiceExists(resourceName, &svcCfg), - resource.TestCheckResourceAttr(resourceName, "private_dns_name", "changed.example.com"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name", domainName2), resource.TestCheckResourceAttr(resourceName, "private_dns_name_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "private_dns_name_configuration.0.type", "TXT"), ), @@ -246,27 +318,23 @@ func testAccCheckVPCEndpointServiceDestroy(s *terraform.State) error { continue } - resp, err := conn.DescribeVpcEndpointServiceConfigurations(&ec2.DescribeVpcEndpointServiceConfigurationsInput{ - ServiceIds: []*string{aws.String(rs.Primary.ID)}, - }) + _, err := tfec2.FindVPCEndpointServiceConfigurationByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + if err != nil { - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, "InvalidVpcEndpointServiceId.NotFound") { - continue - } return err } - if len(resp.ServiceConfigurations) > 0 { - return fmt.Errorf("VPC Endpoint Services still exist.") - } - return err + return fmt.Errorf("EC2 VPC Endpoint Service %s still exists", rs.Primary.ID) } return nil } -func testAccCheckVPCEndpointServiceExists(n string, svcCfg *ec2.ServiceConfiguration) resource.TestCheckFunc { +func testAccCheckVPCEndpointServiceExists(n string, v *ec2.ServiceConfiguration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -274,29 +342,25 @@ func testAccCheckVPCEndpointServiceExists(n string, svcCfg *ec2.ServiceConfigura } if rs.Primary.ID == "" { - return fmt.Errorf("No VPC Endpoint Service ID is set") + return fmt.Errorf("No EC2 VPC Endpoint Service ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - resp, err := conn.DescribeVpcEndpointServiceConfigurations(&ec2.DescribeVpcEndpointServiceConfigurationsInput{ - ServiceIds: []*string{aws.String(rs.Primary.ID)}, - }) + output, err := tfec2.FindVPCEndpointServiceConfigurationByID(conn, rs.Primary.ID) + if err != nil { return err } - if len(resp.ServiceConfigurations) == 0 { - return fmt.Errorf("VPC Endpoint Service not found") - } - *svcCfg = *resp.ServiceConfigurations[0] + *v = *output return nil } } -func testAccVPCEndpointServiceConfig_base(rName1, rName2 string) string { - return fmt.Sprintf(` +func testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName string, count int) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" @@ -305,84 +369,96 @@ resource "aws_vpc" "test" { } } -resource "aws_lb" "test1" { - name = %[1]q - - subnets = [ - aws_subnet.test1.id, - aws_subnet.test2.id, - ] +resource "aws_subnet" "test" { + count = 2 - load_balancer_type = "network" - internal = true - idle_timeout = 60 - enable_deletion_protection = false + vpc_id = aws_vpc.test.id + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) + availability_zone = data.aws_availability_zones.available.names[count.index] tags = { Name = %[1]q } } -resource "aws_lb" "test2" { - name = %[2]q +resource "aws_lb" "test" { + count = %[2]d + + load_balancer_type = "network" + name = "%[1]s-${count.index}" - subnets = [ - aws_subnet.test1.id, - aws_subnet.test2.id, - ] + subnets = aws_subnet.test[*].id - load_balancer_type = "network" internal = true idle_timeout = 60 enable_deletion_protection = false tags = { - Name = %[2]q + Name = %[1]q } } +`, rName, count)) +} -data "aws_availability_zones" "available" { - state = "available" +func testAccVPCEndpointServiceConfig_supportedIPAddressTypesBase(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + assign_generated_ipv6_cidr_block = true - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } -resource "aws_subnet" "test1" { +resource "aws_subnet" "test" { + count = 2 + vpc_id = aws_vpc.test.id - cidr_block = "10.0.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) + availability_zone = data.aws_availability_zones.available.names[count.index] + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, count.index) tags = { Name = %[1]q } } -resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - cidr_block = "10.0.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] +resource "aws_lb" "test" { + load_balancer_type = "network" + name = %[1]q + + subnets = aws_subnet.test[*].id + + internal = true + idle_timeout = 60 + enable_deletion_protection = false + + ip_address_type = "dualstack" tags = { Name = %[1]q } } +`, rName)) +} -data "aws_caller_identity" "current" {} -`, rName1, rName2) +func testAccVPCEndpointServiceConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), ` +resource "aws_vpc_endpoint_service" "test" { + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn +} +`) } func testAccVPCEndpointServiceConfig_gatewayLoadBalancerARNs(rName string, count int) string { - return acctest.ConfigCompose( - acctest.ConfigAvailableAZsNoOptIn(), - fmt.Sprintf(` + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.10.10.0/25" tags = { - Name = "tf-acc-test-load-balancer" + Name = %[1]q } } @@ -392,7 +468,7 @@ resource "aws_subnet" "test" { vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test-load-balancer" + Name = %[1]q } } @@ -410,77 +486,81 @@ resource "aws_lb" "test" { resource "aws_vpc_endpoint_service" "test" { acceptance_required = false gateway_load_balancer_arns = aws_lb.test[*].arn + + tags = { + Name = %[1]q + } } `, rName, count)) } -func testAccVPCEndpointServiceConfig_networkLoadBalancerARNs(rName1, rName2 string) string { - return acctest.ConfigCompose( - testAccVPCEndpointServiceConfig_base(rName1, rName2), - ` +func testAccVPCEndpointServiceConfig_networkLoadBalancerARNs(rName string, count int) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, count), fmt.Sprintf(` resource "aws_vpc_endpoint_service" "test" { - acceptance_required = false + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn - network_load_balancer_arns = [ - aws_lb.test1.arn, - ] + tags = { + Name = %[1]q + } } -`) +`, rName)) } -func testAccVPCEndpointServiceConfig_allowedPrincipals(rName1, rName2 string) string { - return acctest.ConfigCompose( - testAccVPCEndpointServiceConfig_base(rName1, rName2), - fmt.Sprintf(` +func testAccVPCEndpointServiceConfig_supportedIPAddressTypesIPv4(rName string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_supportedIPAddressTypesBase(rName), fmt.Sprintf(` resource "aws_vpc_endpoint_service" "test" { - acceptance_required = false + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn + supported_ip_address_types = ["ipv4"] - network_load_balancer_arns = [ - aws_lb.test1.arn, - ] + tags = { + Name = %[1]q + } +} +`, rName)) +} - allowed_principals = [ - data.aws_caller_identity.current.arn, - ] +func testAccVPCEndpointServiceConfig_supportedIPAddressTypesIPv4AndIPv6(rName string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_supportedIPAddressTypesBase(rName), fmt.Sprintf(` +resource "aws_vpc_endpoint_service" "test" { + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn + supported_ip_address_types = ["ipv4", "ipv6"] tags = { Name = %[1]q } } -`, rName1)) +`, rName)) } -func testAccVPCEndpointServiceConfig_allowedPrincipalsUpdated(rName1, rName2 string) string { - return acctest.ConfigCompose( - testAccVPCEndpointServiceConfig_base(rName1, rName2), - fmt.Sprintf(` -resource "aws_vpc_endpoint_service" "test" { - acceptance_required = true +func testAccVPCEndpointServiceConfig_allowedPrincipals(rName string, count int) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +data "aws_iam_session_context" "current" { + arn = data.aws_caller_identity.current.arn +} - network_load_balancer_arns = [ - aws_lb.test1.arn, - aws_lb.test2.arn, - ] +resource "aws_vpc_endpoint_service" "test" { + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn - allowed_principals = [] + allowed_principals = (%[2]d == 0 ? [] : [data.aws_iam_session_context.current.issuer_arn]) tags = { Name = %[1]q } } -`, rName1)) +`, rName, count)) } -func testAccVPCEndpointServiceConfig_tags1(rName1, rName2, tagKey1, tagValue1 string) string { - return acctest.ConfigCompose( - testAccVPCEndpointServiceConfig_base(rName1, rName2), - fmt.Sprintf(` +func testAccVPCEndpointServiceConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), fmt.Sprintf(` resource "aws_vpc_endpoint_service" "test" { - acceptance_required = false - - network_load_balancer_arns = [ - aws_lb.test1.arn, - ] + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn tags = { %[1]q = %[2]q @@ -489,16 +569,11 @@ resource "aws_vpc_endpoint_service" "test" { `, tagKey1, tagValue1)) } -func testAccVPCEndpointServiceConfig_tags2(rName1, rName2, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return acctest.ConfigCompose( - testAccVPCEndpointServiceConfig_base(rName1, rName2), - fmt.Sprintf(` +func testAccVPCEndpointServiceConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), fmt.Sprintf(` resource "aws_vpc_endpoint_service" "test" { - acceptance_required = false - - network_load_balancer_arns = [ - aws_lb.test1.arn, - ] + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn tags = { %[1]q = %[2]q @@ -508,17 +583,16 @@ resource "aws_vpc_endpoint_service" "test" { `, tagKey1, tagValue1, tagKey2, tagValue2)) } -func testAccVPCEndpointServiceConfig_privateDNSName(rName1, rName2, dnsName string) string { - return acctest.ConfigCompose( - testAccVPCEndpointServiceConfig_base(rName1, rName2), - fmt.Sprintf(` +func testAccVPCEndpointServiceConfig_privateDNSName(rName, dnsName string) string { + return acctest.ConfigCompose(testAccVPCEndpointServiceConfig_networkLoadBalancerBase(rName, 1), fmt.Sprintf(` resource "aws_vpc_endpoint_service" "test" { - acceptance_required = false - private_dns_name = "%s" + acceptance_required = false + network_load_balancer_arns = aws_lb.test[*].arn + private_dns_name = %[2]q - network_load_balancer_arns = [ - aws_lb.test1.arn, - ] + tags = { + Name = %[1]q + } } -`, dnsName)) +`, rName, dnsName)) } diff --git a/internal/service/ec2/vpc_endpoint_subnet_association.go b/internal/service/ec2/vpc_endpoint_subnet_association.go index 793eea6ef27c..f28e4631310d 100644 --- a/internal/service/ec2/vpc_endpoint_subnet_association.go +++ b/internal/service/ec2/vpc_endpoint_subnet_association.go @@ -131,7 +131,7 @@ func resourceVPCEndpointSubnetAssociationDelete(d *schema.ResourceData, meta int log.Printf("[DEBUG] Deleting VPC Endpoint Subnet Association: %s", id) _, err := conn.ModifyVpcEndpoint(input) - if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIDNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidSubnetIdNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidParameter) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIdNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidSubnetIdNotFound) || tfawserr.ErrCodeEquals(err, errCodeInvalidParameter) { return nil } diff --git a/internal/service/ec2/vpnclient_authorization_rule.go b/internal/service/ec2/vpnclient_authorization_rule.go index 402b8b6379ac..93c7356de68b 100644 --- a/internal/service/ec2/vpnclient_authorization_rule.go +++ b/internal/service/ec2/vpnclient_authorization_rule.go @@ -157,7 +157,7 @@ func resourceClientVPNAuthorizationRuleDelete(d *schema.ResourceData, meta inter log.Printf("[DEBUG] Deleting EC2 Client VPN Authorization Rule: %s", d.Id()) _, err = conn.RevokeClientVpnIngress(input) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound, errCodeInvalidClientVPNAuthorizationRuleNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound, errCodeInvalidClientVPNAuthorizationRuleNotFound) { return nil } diff --git a/internal/service/ec2/vpnclient_endpoint.go b/internal/service/ec2/vpnclient_endpoint.go index b38f33268ba7..4394da1e2e57 100644 --- a/internal/service/ec2/vpnclient_endpoint.go +++ b/internal/service/ec2/vpnclient_endpoint.go @@ -470,7 +470,7 @@ func resourceClientVPNEndpointDelete(d *schema.ResourceData, meta interface{}) e ClientVpnEndpointId: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { return nil } diff --git a/internal/service/ec2/vpnclient_network_association.go b/internal/service/ec2/vpnclient_network_association.go index 3d3c1413c70e..0f02b33d558d 100644 --- a/internal/service/ec2/vpnclient_network_association.go +++ b/internal/service/ec2/vpnclient_network_association.go @@ -165,7 +165,7 @@ func resourceClientVPNNetworkAssociationDelete(d *schema.ResourceData, meta inte AssociationId: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNAssociationIDNotFound, errCodeInvalidClientVPNEndpointIDNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNAssociationIdNotFound, errCodeInvalidClientVPNEndpointIdNotFound) { return nil } diff --git a/internal/service/ec2/vpnclient_route.go b/internal/service/ec2/vpnclient_route.go index 94d8405a0d44..5105d14c9e93 100644 --- a/internal/service/ec2/vpnclient_route.go +++ b/internal/service/ec2/vpnclient_route.go @@ -144,7 +144,7 @@ func resourceClientVPNRouteDelete(d *schema.ResourceData, meta interface{}) erro TargetVpcSubnetId: aws.String(targetSubnetID), }) - if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIDNotFound, errCodeInvalidClientVPNRouteNotFound) { + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound, errCodeInvalidClientVPNRouteNotFound) { return nil } diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index 5ad62e86c06a..7e9c9c54535a 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -2266,8 +2266,8 @@ func WaitVPCEndpointDeleted(conn *ec2.EC2, vpcEndpointID string, timeout time.Du stateConf := &resource.StateChangeConf{ Pending: []string{vpcEndpointStateDeleting}, Target: []string{}, - Timeout: timeout, Refresh: StatusVPCEndpointState(conn, vpcEndpointID), + Timeout: timeout, Delay: 5 * time.Second, MinTimeout: 5 * time.Second, } @@ -2281,6 +2281,44 @@ func WaitVPCEndpointDeleted(conn *ec2.EC2, vpcEndpointID string, timeout time.Du return nil, err } +func WaitVPCEndpointServiceAvailable(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.ServiceConfiguration, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ServiceStatePending}, + Target: []string{ec2.ServiceStateAvailable}, + Refresh: StatusVPCEndpointServiceStateAvailable(conn, id), + Timeout: timeout, + Delay: 5 * time.Second, + MinTimeout: 5 * time.Second, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.ServiceConfiguration); ok { + return output, err + } + + return nil, err +} + +func WaitVPCEndpointServiceDeleted(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.ServiceConfiguration, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.ServiceStateAvailable, ec2.ServiceStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: StatusVPCEndpointServiceStateDeleted(conn, id), + Delay: 5 * time.Second, + MinTimeout: 5 * time.Second, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.ServiceConfiguration); ok { + return output, err + } + + return nil, err +} + func WaitVPCEndpointRouteTableAssociationDeleted(conn *ec2.EC2, vpcEndpointID, routeTableID string) error { stateConf := &resource.StateChangeConf{ Pending: []string{VPCEndpointRouteTableAssociationStatusReady}, diff --git a/website/docs/d/vpc_endpoint_service.html.markdown b/website/docs/d/vpc_endpoint_service.html.markdown index c50811477c94..51fea7d274ee 100644 --- a/website/docs/d/vpc_endpoint_service.html.markdown +++ b/website/docs/d/vpc_endpoint_service.html.markdown @@ -85,5 +85,6 @@ In addition to all arguments above, the following attributes are exported: * `owner` - The AWS account ID of the service owner or `amazon`. * `private_dns_name` - The private DNS name for the service. * `service_id` - The ID of the endpoint service. +* `supported_ip_address_types` - The supported IP address types. * `tags` - A map of tags assigned to the resource. * `vpc_endpoint_policy_supported` - Whether or not the service supports endpoint policies - `true` or `false`. diff --git a/website/docs/r/vpc_endpoint_service.html.markdown b/website/docs/r/vpc_endpoint_service.html.markdown index 4aea6f963d32..c3715132cc00 100644 --- a/website/docs/r/vpc_endpoint_service.html.markdown +++ b/website/docs/r/vpc_endpoint_service.html.markdown @@ -47,6 +47,7 @@ The following arguments are supported: * `network_load_balancer_arns` - (Optional) Amazon Resource Names (ARNs) of one or more Network Load Balancers for the endpoint service. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `private_dns_name` - (Optional) The private DNS name for the service. +* `supported_ip_address_types` - (Optional) The supported IP address types. The possible values are `ipv4` and `ipv6`. ## Attributes Reference