diff --git a/.changelog/22756.txt b/.changelog/22756.txt new file mode 100644 index 00000000000..4330995073f --- /dev/null +++ b/.changelog/22756.txt @@ -0,0 +1,27 @@ +```release-note:new-resource +aws_ec2_transit_gateway_multicast_domain +``` + +```release-note:new-resource +aws_ec2_transit_gateway_multicast_domain_association +``` + +```release-note:new-resource +aws_ec2_transit_gateway_multicast_group_member +``` + +```release-note:new-resource +aws_ec2_transit_gateway_multicast_group_source +``` + +```release-note:new-data-source +aws_ec2_transit_gateway_multicast_domain +``` + +```release-note:enhancement +resource/aws_ec2_transit_gateway: Add `multicast_support` argument +``` + +```release-note:enhancement +data-source/aws_ec2_transit_gateway: Add `multicast_support` attribute +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f855770a797..ecad29a1638 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -536,6 +536,7 @@ func Provider() *schema.Provider { "aws_ec2_spot_price": ec2.DataSourceSpotPrice(), "aws_ec2_transit_gateway": ec2.DataSourceTransitGateway(), "aws_ec2_transit_gateway_dx_gateway_attachment": ec2.DataSourceTransitGatewayDxGatewayAttachment(), + "aws_ec2_transit_gateway_multicast_domain": ec2.DataSourceTransitGatewayMulticastDomain(), "aws_ec2_transit_gateway_peering_attachment": ec2.DataSourceTransitGatewayPeeringAttachment(), "aws_ec2_transit_gateway_route_table": ec2.DataSourceTransitGatewayRouteTable(), "aws_ec2_transit_gateway_route_tables": ec2.DataSourceTransitGatewayRouteTables(), @@ -1168,109 +1169,113 @@ func Provider() *schema.Provider { "aws_dynamodb_table_item": dynamodb.ResourceTableItem(), "aws_dynamodb_tag": dynamodb.ResourceTag(), - "aws_ami": ec2.ResourceAMI(), - "aws_ami_copy": ec2.ResourceAMICopy(), - "aws_ami_from_instance": ec2.ResourceAMIFromInstance(), - "aws_ami_launch_permission": ec2.ResourceAMILaunchPermission(), - "aws_customer_gateway": ec2.ResourceCustomerGateway(), - "aws_default_network_acl": ec2.ResourceDefaultNetworkACL(), - "aws_default_route_table": ec2.ResourceDefaultRouteTable(), - "aws_default_security_group": ec2.ResourceDefaultSecurityGroup(), - "aws_default_subnet": ec2.ResourceDefaultSubnet(), - "aws_default_vpc": ec2.ResourceDefaultVPC(), - "aws_default_vpc_dhcp_options": ec2.ResourceDefaultVPCDHCPOptions(), - "aws_ebs_default_kms_key": ec2.ResourceEBSDefaultKMSKey(), - "aws_ebs_encryption_by_default": ec2.ResourceEBSEncryptionByDefault(), - "aws_ebs_snapshot": ec2.ResourceEBSSnapshot(), - "aws_ebs_snapshot_copy": ec2.ResourceEBSSnapshotCopy(), - "aws_ebs_snapshot_import": ec2.ResourceEBSSnapshotImport(), - "aws_ebs_volume": ec2.ResourceEBSVolume(), - "aws_ec2_availability_zone_group": ec2.ResourceAvailabilityZoneGroup(), - "aws_ec2_capacity_reservation": ec2.ResourceCapacityReservation(), - "aws_ec2_carrier_gateway": ec2.ResourceCarrierGateway(), - "aws_ec2_client_vpn_authorization_rule": ec2.ResourceClientVPNAuthorizationRule(), - "aws_ec2_client_vpn_endpoint": ec2.ResourceClientVPNEndpoint(), - "aws_ec2_client_vpn_network_association": ec2.ResourceClientVPNNetworkAssociation(), - "aws_ec2_client_vpn_route": ec2.ResourceClientVPNRoute(), - "aws_ec2_fleet": ec2.ResourceFleet(), - "aws_ec2_host": ec2.ResourceHost(), - "aws_ec2_local_gateway_route": ec2.ResourceLocalGatewayRoute(), - "aws_ec2_local_gateway_route_table_vpc_association": ec2.ResourceLocalGatewayRouteTableVPCAssociation(), - "aws_ec2_managed_prefix_list": ec2.ResourceManagedPrefixList(), - "aws_ec2_managed_prefix_list_entry": ec2.ResourceManagedPrefixListEntry(), - "aws_ec2_subnet_cidr_reservation": ec2.ResourceSubnetCIDRReservation(), - "aws_ec2_tag": ec2.ResourceTag(), - "aws_ec2_traffic_mirror_filter": ec2.ResourceTrafficMirrorFilter(), - "aws_ec2_traffic_mirror_filter_rule": ec2.ResourceTrafficMirrorFilterRule(), - "aws_ec2_traffic_mirror_session": ec2.ResourceTrafficMirrorSession(), - "aws_ec2_traffic_mirror_target": ec2.ResourceTrafficMirrorTarget(), - "aws_ec2_transit_gateway": ec2.ResourceTransitGateway(), - "aws_ec2_transit_gateway_peering_attachment": ec2.ResourceTransitGatewayPeeringAttachment(), - "aws_ec2_transit_gateway_peering_attachment_accepter": ec2.ResourceTransitGatewayPeeringAttachmentAccepter(), - "aws_ec2_transit_gateway_prefix_list_reference": ec2.ResourceTransitGatewayPrefixListReference(), - "aws_ec2_transit_gateway_route": ec2.ResourceTransitGatewayRoute(), - "aws_ec2_transit_gateway_route_table": ec2.ResourceTransitGatewayRouteTable(), - "aws_ec2_transit_gateway_route_table_association": ec2.ResourceTransitGatewayRouteTableAssociation(), - "aws_ec2_transit_gateway_route_table_propagation": ec2.ResourceTransitGatewayRouteTablePropagation(), - "aws_ec2_transit_gateway_vpc_attachment": ec2.ResourceTransitGatewayVPCAttachment(), - "aws_ec2_transit_gateway_vpc_attachment_accepter": ec2.ResourceTransitGatewayVPCAttachmentAccepter(), - "aws_egress_only_internet_gateway": ec2.ResourceEgressOnlyInternetGateway(), - "aws_eip": ec2.ResourceEIP(), - "aws_eip_association": ec2.ResourceEIPAssociation(), - "aws_flow_log": ec2.ResourceFlowLog(), - "aws_instance": ec2.ResourceInstance(), - "aws_internet_gateway": ec2.ResourceInternetGateway(), - "aws_internet_gateway_attachment": ec2.ResourceInternetGatewayAttachment(), - "aws_key_pair": ec2.ResourceKeyPair(), - "aws_launch_template": ec2.ResourceLaunchTemplate(), - "aws_main_route_table_association": ec2.ResourceMainRouteTableAssociation(), - "aws_nat_gateway": ec2.ResourceNATGateway(), - "aws_network_acl": ec2.ResourceNetworkACL(), - "aws_network_acl_association": ec2.ResourceNetworkACLAssociation(), - "aws_network_acl_rule": ec2.ResourceNetworkACLRule(), - "aws_network_interface": ec2.ResourceNetworkInterface(), - "aws_network_interface_attachment": ec2.ResourceNetworkInterfaceAttachment(), - "aws_network_interface_sg_attachment": ec2.ResourceNetworkInterfaceSGAttachment(), - "aws_placement_group": ec2.ResourcePlacementGroup(), - "aws_route": ec2.ResourceRoute(), - "aws_route_table": ec2.ResourceRouteTable(), - "aws_route_table_association": ec2.ResourceRouteTableAssociation(), - "aws_security_group": ec2.ResourceSecurityGroup(), - "aws_security_group_rule": ec2.ResourceSecurityGroupRule(), - "aws_snapshot_create_volume_permission": ec2.ResourceSnapshotCreateVolumePermission(), - "aws_spot_datafeed_subscription": ec2.ResourceSpotDataFeedSubscription(), - "aws_spot_fleet_request": ec2.ResourceSpotFleetRequest(), - "aws_spot_instance_request": ec2.ResourceSpotInstanceRequest(), - "aws_subnet": ec2.ResourceSubnet(), - "aws_volume_attachment": ec2.ResourceVolumeAttachment(), - "aws_vpc": ec2.ResourceVPC(), - "aws_vpc_dhcp_options": ec2.ResourceVPCDHCPOptions(), - "aws_vpc_dhcp_options_association": ec2.ResourceVPCDHCPOptionsAssociation(), - "aws_vpc_endpoint": ec2.ResourceVPCEndpoint(), - "aws_vpc_endpoint_connection_accepter": ec2.ResourceVPCEndpointConnectionAccepter(), - "aws_vpc_endpoint_connection_notification": ec2.ResourceVPCEndpointConnectionNotification(), - "aws_vpc_endpoint_policy": ec2.ResourceVPCEndpointPolicy(), - "aws_vpc_endpoint_route_table_association": ec2.ResourceVPCEndpointRouteTableAssociation(), - "aws_vpc_endpoint_service": ec2.ResourceVPCEndpointService(), - "aws_vpc_endpoint_service_allowed_principal": ec2.ResourceVPCEndpointServiceAllowedPrincipal(), - "aws_vpc_endpoint_subnet_association": ec2.ResourceVPCEndpointSubnetAssociation(), - "aws_vpc_ipam": ec2.ResourceVPCIpam(), - "aws_vpc_ipam_organization_admin_account": ec2.ResourceVPCIpamOrganizationAdminAccount(), - "aws_vpc_ipam_pool": ec2.ResourceVPCIpamPool(), - "aws_vpc_ipam_pool_cidr_allocation": ec2.ResourceVPCIpamPoolCidrAllocation(), - "aws_vpc_ipam_pool_cidr": ec2.ResourceVPCIpamPoolCidr(), - "aws_vpc_ipam_preview_next_cidr": ec2.ResourceVPCIpamPreviewNextCidr(), - "aws_vpc_ipam_scope": ec2.ResourceVPCIpamScope(), - "aws_vpc_ipv4_cidr_block_association": ec2.ResourceVPCIPv4CIDRBlockAssociation(), - "aws_vpc_ipv6_cidr_block_association": ec2.ResourceVPCIPv6CIDRBlockAssociation(), - "aws_vpc_peering_connection": ec2.ResourceVPCPeeringConnection(), - "aws_vpc_peering_connection_accepter": ec2.ResourceVPCPeeringConnectionAccepter(), - "aws_vpc_peering_connection_options": ec2.ResourceVPCPeeringConnectionOptions(), - "aws_vpn_connection": ec2.ResourceVPNConnection(), - "aws_vpn_connection_route": ec2.ResourceVPNConnectionRoute(), - "aws_vpn_gateway": ec2.ResourceVPNGateway(), - "aws_vpn_gateway_attachment": ec2.ResourceVPNGatewayAttachment(), - "aws_vpn_gateway_route_propagation": ec2.ResourceVPNGatewayRoutePropagation(), + "aws_ami": ec2.ResourceAMI(), + "aws_ami_copy": ec2.ResourceAMICopy(), + "aws_ami_from_instance": ec2.ResourceAMIFromInstance(), + "aws_ami_launch_permission": ec2.ResourceAMILaunchPermission(), + "aws_customer_gateway": ec2.ResourceCustomerGateway(), + "aws_default_network_acl": ec2.ResourceDefaultNetworkACL(), + "aws_default_route_table": ec2.ResourceDefaultRouteTable(), + "aws_default_security_group": ec2.ResourceDefaultSecurityGroup(), + "aws_default_subnet": ec2.ResourceDefaultSubnet(), + "aws_default_vpc": ec2.ResourceDefaultVPC(), + "aws_default_vpc_dhcp_options": ec2.ResourceDefaultVPCDHCPOptions(), + "aws_ebs_default_kms_key": ec2.ResourceEBSDefaultKMSKey(), + "aws_ebs_encryption_by_default": ec2.ResourceEBSEncryptionByDefault(), + "aws_ebs_snapshot": ec2.ResourceEBSSnapshot(), + "aws_ebs_snapshot_copy": ec2.ResourceEBSSnapshotCopy(), + "aws_ebs_snapshot_import": ec2.ResourceEBSSnapshotImport(), + "aws_ebs_volume": ec2.ResourceEBSVolume(), + "aws_ec2_availability_zone_group": ec2.ResourceAvailabilityZoneGroup(), + "aws_ec2_capacity_reservation": ec2.ResourceCapacityReservation(), + "aws_ec2_carrier_gateway": ec2.ResourceCarrierGateway(), + "aws_ec2_client_vpn_authorization_rule": ec2.ResourceClientVPNAuthorizationRule(), + "aws_ec2_client_vpn_endpoint": ec2.ResourceClientVPNEndpoint(), + "aws_ec2_client_vpn_network_association": ec2.ResourceClientVPNNetworkAssociation(), + "aws_ec2_client_vpn_route": ec2.ResourceClientVPNRoute(), + "aws_ec2_fleet": ec2.ResourceFleet(), + "aws_ec2_host": ec2.ResourceHost(), + "aws_ec2_local_gateway_route": ec2.ResourceLocalGatewayRoute(), + "aws_ec2_local_gateway_route_table_vpc_association": ec2.ResourceLocalGatewayRouteTableVPCAssociation(), + "aws_ec2_managed_prefix_list": ec2.ResourceManagedPrefixList(), + "aws_ec2_managed_prefix_list_entry": ec2.ResourceManagedPrefixListEntry(), + "aws_ec2_subnet_cidr_reservation": ec2.ResourceSubnetCIDRReservation(), + "aws_ec2_tag": ec2.ResourceTag(), + "aws_ec2_traffic_mirror_filter": ec2.ResourceTrafficMirrorFilter(), + "aws_ec2_traffic_mirror_filter_rule": ec2.ResourceTrafficMirrorFilterRule(), + "aws_ec2_traffic_mirror_session": ec2.ResourceTrafficMirrorSession(), + "aws_ec2_traffic_mirror_target": ec2.ResourceTrafficMirrorTarget(), + "aws_ec2_transit_gateway": ec2.ResourceTransitGateway(), + "aws_ec2_transit_gateway_multicast_domain": ec2.ResourceTransitGatewayMulticastDomain(), + "aws_ec2_transit_gateway_multicast_domain_association": ec2.ResourceTransitGatewayMulticastDomainAssociation(), + "aws_ec2_transit_gateway_multicast_group_member": ec2.ResourceTransitGatewayMulticastGroupMember(), + "aws_ec2_transit_gateway_multicast_group_source": ec2.ResourceTransitGatewayMulticastGroupSource(), + "aws_ec2_transit_gateway_peering_attachment": ec2.ResourceTransitGatewayPeeringAttachment(), + "aws_ec2_transit_gateway_peering_attachment_accepter": ec2.ResourceTransitGatewayPeeringAttachmentAccepter(), + "aws_ec2_transit_gateway_prefix_list_reference": ec2.ResourceTransitGatewayPrefixListReference(), + "aws_ec2_transit_gateway_route": ec2.ResourceTransitGatewayRoute(), + "aws_ec2_transit_gateway_route_table": ec2.ResourceTransitGatewayRouteTable(), + "aws_ec2_transit_gateway_route_table_association": ec2.ResourceTransitGatewayRouteTableAssociation(), + "aws_ec2_transit_gateway_route_table_propagation": ec2.ResourceTransitGatewayRouteTablePropagation(), + "aws_ec2_transit_gateway_vpc_attachment": ec2.ResourceTransitGatewayVPCAttachment(), + "aws_ec2_transit_gateway_vpc_attachment_accepter": ec2.ResourceTransitGatewayVPCAttachmentAccepter(), + "aws_egress_only_internet_gateway": ec2.ResourceEgressOnlyInternetGateway(), + "aws_eip": ec2.ResourceEIP(), + "aws_eip_association": ec2.ResourceEIPAssociation(), + "aws_flow_log": ec2.ResourceFlowLog(), + "aws_instance": ec2.ResourceInstance(), + "aws_internet_gateway": ec2.ResourceInternetGateway(), + "aws_internet_gateway_attachment": ec2.ResourceInternetGatewayAttachment(), + "aws_key_pair": ec2.ResourceKeyPair(), + "aws_launch_template": ec2.ResourceLaunchTemplate(), + "aws_main_route_table_association": ec2.ResourceMainRouteTableAssociation(), + "aws_nat_gateway": ec2.ResourceNATGateway(), + "aws_network_acl": ec2.ResourceNetworkACL(), + "aws_network_acl_association": ec2.ResourceNetworkACLAssociation(), + "aws_network_acl_rule": ec2.ResourceNetworkACLRule(), + "aws_network_interface": ec2.ResourceNetworkInterface(), + "aws_network_interface_attachment": ec2.ResourceNetworkInterfaceAttachment(), + "aws_network_interface_sg_attachment": ec2.ResourceNetworkInterfaceSGAttachment(), + "aws_placement_group": ec2.ResourcePlacementGroup(), + "aws_route": ec2.ResourceRoute(), + "aws_route_table": ec2.ResourceRouteTable(), + "aws_route_table_association": ec2.ResourceRouteTableAssociation(), + "aws_security_group": ec2.ResourceSecurityGroup(), + "aws_security_group_rule": ec2.ResourceSecurityGroupRule(), + "aws_snapshot_create_volume_permission": ec2.ResourceSnapshotCreateVolumePermission(), + "aws_spot_datafeed_subscription": ec2.ResourceSpotDataFeedSubscription(), + "aws_spot_fleet_request": ec2.ResourceSpotFleetRequest(), + "aws_spot_instance_request": ec2.ResourceSpotInstanceRequest(), + "aws_subnet": ec2.ResourceSubnet(), + "aws_volume_attachment": ec2.ResourceVolumeAttachment(), + "aws_vpc": ec2.ResourceVPC(), + "aws_vpc_dhcp_options": ec2.ResourceVPCDHCPOptions(), + "aws_vpc_dhcp_options_association": ec2.ResourceVPCDHCPOptionsAssociation(), + "aws_vpc_endpoint": ec2.ResourceVPCEndpoint(), + "aws_vpc_endpoint_connection_accepter": ec2.ResourceVPCEndpointConnectionAccepter(), + "aws_vpc_endpoint_connection_notification": ec2.ResourceVPCEndpointConnectionNotification(), + "aws_vpc_endpoint_policy": ec2.ResourceVPCEndpointPolicy(), + "aws_vpc_endpoint_route_table_association": ec2.ResourceVPCEndpointRouteTableAssociation(), + "aws_vpc_endpoint_service": ec2.ResourceVPCEndpointService(), + "aws_vpc_endpoint_service_allowed_principal": ec2.ResourceVPCEndpointServiceAllowedPrincipal(), + "aws_vpc_endpoint_subnet_association": ec2.ResourceVPCEndpointSubnetAssociation(), + "aws_vpc_ipam": ec2.ResourceVPCIpam(), + "aws_vpc_ipam_organization_admin_account": ec2.ResourceVPCIpamOrganizationAdminAccount(), + "aws_vpc_ipam_pool": ec2.ResourceVPCIpamPool(), + "aws_vpc_ipam_pool_cidr_allocation": ec2.ResourceVPCIpamPoolCidrAllocation(), + "aws_vpc_ipam_pool_cidr": ec2.ResourceVPCIpamPoolCidr(), + "aws_vpc_ipam_preview_next_cidr": ec2.ResourceVPCIpamPreviewNextCidr(), + "aws_vpc_ipam_scope": ec2.ResourceVPCIpamScope(), + "aws_vpc_ipv4_cidr_block_association": ec2.ResourceVPCIPv4CIDRBlockAssociation(), + "aws_vpc_ipv6_cidr_block_association": ec2.ResourceVPCIPv6CIDRBlockAssociation(), + "aws_vpc_peering_connection": ec2.ResourceVPCPeeringConnection(), + "aws_vpc_peering_connection_accepter": ec2.ResourceVPCPeeringConnectionAccepter(), + "aws_vpc_peering_connection_options": ec2.ResourceVPCPeeringConnectionOptions(), + "aws_vpn_connection": ec2.ResourceVPNConnection(), + "aws_vpn_connection_route": ec2.ResourceVPNConnectionRoute(), + "aws_vpn_gateway": ec2.ResourceVPNGateway(), + "aws_vpn_gateway_attachment": ec2.ResourceVPNGatewayAttachment(), + "aws_vpn_gateway_route_propagation": ec2.ResourceVPNGatewayRoutePropagation(), "aws_ecr_lifecycle_policy": ecr.ResourceLifecyclePolicy(), "aws_ecr_pull_through_cache_rule": ecr.ResourcePullThroughCacheRule(), diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index d854280d415..985c7d04818 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -10,68 +10,69 @@ import ( ) const ( - ErrCodeAuthFailure = "AuthFailure" - ErrCodeClientInvalidHostIDNotFound = "Client.InvalidHostID.NotFound" - ErrCodeDefaultSubnetAlreadyExistsInAvailabilityZone = "DefaultSubnetAlreadyExistsInAvailabilityZone" - ErrCodeDependencyViolation = "DependencyViolation" - ErrCodeGatewayNotAttached = "Gateway.NotAttached" - ErrCodeIncorrectState = "IncorrectState" - ErrCodeInvalidAddressNotFound = "InvalidAddress.NotFound" - ErrCodeInvalidAllocationIDNotFound = "InvalidAllocationID.NotFound" - ErrCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" - ErrCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" - ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound" - ErrCodeInvalidClientVpnActiveAssociationNotFound = "InvalidClientVpnActiveAssociationNotFound" - ErrCodeInvalidClientVpnAssociationIdNotFound = "InvalidClientVpnAssociationIdNotFound" - ErrCodeInvalidClientVpnAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" - ErrCodeInvalidClientVpnEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" - ErrCodeInvalidClientVpnRouteNotFound = "InvalidClientVpnRouteNotFound" - ErrCodeInvalidCustomerGatewayIDNotFound = "InvalidCustomerGatewayID.NotFound" - ErrCodeInvalidDhcpOptionIDNotFound = "InvalidDhcpOptionID.NotFound" - ErrCodeInvalidFlowLogIdNotFound = "InvalidFlowLogId.NotFound" - ErrCodeInvalidGatewayIDNotFound = "InvalidGatewayID.NotFound" - ErrCodeInvalidGroupNotFound = "InvalidGroup.NotFound" - ErrCodeInvalidHostIDNotFound = "InvalidHostID.NotFound" - ErrCodeInvalidInstanceIDNotFound = "InvalidInstanceID.NotFound" - ErrCodeInvalidInternetGatewayIDNotFound = "InvalidInternetGatewayID.NotFound" - ErrCodeInvalidKeyPairNotFound = "InvalidKeyPair.NotFound" - ErrCodeInvalidNetworkAclEntryNotFound = "InvalidNetworkAclEntry.NotFound" - ErrCodeInvalidNetworkAclIDNotFound = "InvalidNetworkAclID.NotFound" - ErrCodeInvalidNetworkInterfaceIDNotFound = "InvalidNetworkInterfaceID.NotFound" - ErrCodeInvalidParameter = "InvalidParameter" - ErrCodeInvalidParameterException = "InvalidParameterException" - ErrCodeInvalidParameterValue = "InvalidParameterValue" - ErrCodeInvalidPermissionDuplicate = "InvalidPermission.Duplicate" - ErrCodeInvalidPermissionMalformed = "InvalidPermission.Malformed" - ErrCodeInvalidPermissionNotFound = "InvalidPermission.NotFound" - ErrCodeInvalidPlacementGroupUnknown = "InvalidPlacementGroup.Unknown" - ErrCodeInvalidPoolIDNotFound = "InvalidPoolID.NotFound" - ErrCodeInvalidPrefixListIDNotFound = "InvalidPrefixListID.NotFound" - ErrCodeInvalidRouteNotFound = "InvalidRoute.NotFound" - ErrCodeInvalidRouteTableIDNotFound = "InvalidRouteTableID.NotFound" - ErrCodeInvalidRouteTableIdNotFound = "InvalidRouteTableId.NotFound" - ErrCodeInvalidSecurityGroupIDNotFound = "InvalidSecurityGroupID.NotFound" - ErrCodeInvalidSnapshotInUse = "InvalidSnapshot.InUse" - ErrCodeInvalidSnapshotNotFound = "InvalidSnapshot.NotFound" - ErrCodeInvalidSpotDatafeedNotFound = "InvalidSpotDatafeed.NotFound" - ErrCodeInvalidSpotInstanceRequestIDNotFound = "InvalidSpotInstanceRequestID.NotFound" - ErrCodeInvalidSubnetCidrReservationIDNotFound = "InvalidSubnetCidrReservationID.NotFound" - ErrCodeInvalidSubnetIDNotFound = "InvalidSubnetID.NotFound" - ErrCodeInvalidSubnetIdNotFound = "InvalidSubnetId.NotFound" - ErrCodeInvalidTransitGatewayAttachmentIDNotFound = "InvalidTransitGatewayAttachmentID.NotFound" - ErrCodeInvalidTransitGatewayIDNotFound = "InvalidTransitGatewayID.NotFound" - ErrCodeInvalidVolumeNotFound = "InvalidVolume.NotFound" - ErrCodeInvalidVpcCidrBlockAssociationIDNotFound = "InvalidVpcCidrBlockAssociationID.NotFound" - ErrCodeInvalidVpcEndpointIdNotFound = "InvalidVpcEndpointId.NotFound" - ErrCodeInvalidVpcEndpointNotFound = "InvalidVpcEndpoint.NotFound" - ErrCodeInvalidVpcEndpointServiceIdNotFound = "InvalidVpcEndpointServiceId.NotFound" - ErrCodeInvalidVpcIDNotFound = "InvalidVpcID.NotFound" - ErrCodeInvalidVpcPeeringConnectionIDNotFound = "InvalidVpcPeeringConnectionID.NotFound" - ErrCodeInvalidVpnConnectionIDNotFound = "InvalidVpnConnectionID.NotFound" - ErrCodeInvalidVpnGatewayAttachmentNotFound = "InvalidVpnGatewayAttachment.NotFound" - ErrCodeInvalidVpnGatewayIDNotFound = "InvalidVpnGatewayID.NotFound" - ErrCodeNatGatewayNotFound = "NatGatewayNotFound" - ErrCodeUnsupportedOperation = "UnsupportedOperation" + ErrCodeAuthFailure = "AuthFailure" + ErrCodeClientInvalidHostIDNotFound = "Client.InvalidHostID.NotFound" + ErrCodeDefaultSubnetAlreadyExistsInAvailabilityZone = "DefaultSubnetAlreadyExistsInAvailabilityZone" + ErrCodeDependencyViolation = "DependencyViolation" + ErrCodeGatewayNotAttached = "Gateway.NotAttached" + ErrCodeIncorrectState = "IncorrectState" + ErrCodeInvalidAddressNotFound = "InvalidAddress.NotFound" + ErrCodeInvalidAllocationIDNotFound = "InvalidAllocationID.NotFound" + ErrCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" + ErrCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" + ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound" + ErrCodeInvalidClientVpnActiveAssociationNotFound = "InvalidClientVpnActiveAssociationNotFound" + ErrCodeInvalidClientVpnAssociationIdNotFound = "InvalidClientVpnAssociationIdNotFound" + ErrCodeInvalidClientVpnAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" + ErrCodeInvalidClientVpnEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" + ErrCodeInvalidClientVpnRouteNotFound = "InvalidClientVpnRouteNotFound" + ErrCodeInvalidCustomerGatewayIDNotFound = "InvalidCustomerGatewayID.NotFound" + ErrCodeInvalidDhcpOptionIDNotFound = "InvalidDhcpOptionID.NotFound" + ErrCodeInvalidFlowLogIdNotFound = "InvalidFlowLogId.NotFound" + ErrCodeInvalidGatewayIDNotFound = "InvalidGatewayID.NotFound" + ErrCodeInvalidGroupNotFound = "InvalidGroup.NotFound" + ErrCodeInvalidHostIDNotFound = "InvalidHostID.NotFound" + ErrCodeInvalidInstanceIDNotFound = "InvalidInstanceID.NotFound" + ErrCodeInvalidInternetGatewayIDNotFound = "InvalidInternetGatewayID.NotFound" + ErrCodeInvalidKeyPairNotFound = "InvalidKeyPair.NotFound" + ErrCodeInvalidNetworkAclEntryNotFound = "InvalidNetworkAclEntry.NotFound" + ErrCodeInvalidNetworkAclIDNotFound = "InvalidNetworkAclID.NotFound" + ErrCodeInvalidNetworkInterfaceIDNotFound = "InvalidNetworkInterfaceID.NotFound" + ErrCodeInvalidParameter = "InvalidParameter" + ErrCodeInvalidParameterException = "InvalidParameterException" + ErrCodeInvalidParameterValue = "InvalidParameterValue" + ErrCodeInvalidPermissionDuplicate = "InvalidPermission.Duplicate" + ErrCodeInvalidPermissionMalformed = "InvalidPermission.Malformed" + ErrCodeInvalidPermissionNotFound = "InvalidPermission.NotFound" + ErrCodeInvalidPlacementGroupUnknown = "InvalidPlacementGroup.Unknown" + ErrCodeInvalidPoolIDNotFound = "InvalidPoolID.NotFound" + ErrCodeInvalidPrefixListIDNotFound = "InvalidPrefixListID.NotFound" + ErrCodeInvalidRouteNotFound = "InvalidRoute.NotFound" + ErrCodeInvalidRouteTableIDNotFound = "InvalidRouteTableID.NotFound" + ErrCodeInvalidRouteTableIdNotFound = "InvalidRouteTableId.NotFound" + ErrCodeInvalidSecurityGroupIDNotFound = "InvalidSecurityGroupID.NotFound" + ErrCodeInvalidSnapshotInUse = "InvalidSnapshot.InUse" + ErrCodeInvalidSnapshotNotFound = "InvalidSnapshot.NotFound" + ErrCodeInvalidSpotDatafeedNotFound = "InvalidSpotDatafeed.NotFound" + ErrCodeInvalidSpotInstanceRequestIDNotFound = "InvalidSpotInstanceRequestID.NotFound" + ErrCodeInvalidSubnetCidrReservationIDNotFound = "InvalidSubnetCidrReservationID.NotFound" + ErrCodeInvalidSubnetIDNotFound = "InvalidSubnetID.NotFound" + ErrCodeInvalidSubnetIdNotFound = "InvalidSubnetId.NotFound" + ErrCodeInvalidTransitGatewayAttachmentIDNotFound = "InvalidTransitGatewayAttachmentID.NotFound" + ErrCodeInvalidTransitGatewayIDNotFound = "InvalidTransitGatewayID.NotFound" + ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound = "InvalidTransitGatewayMulticastDomainId.NotFound" + ErrCodeInvalidVolumeNotFound = "InvalidVolume.NotFound" + ErrCodeInvalidVpcCidrBlockAssociationIDNotFound = "InvalidVpcCidrBlockAssociationID.NotFound" + ErrCodeInvalidVpcEndpointIdNotFound = "InvalidVpcEndpointId.NotFound" + ErrCodeInvalidVpcEndpointNotFound = "InvalidVpcEndpoint.NotFound" + ErrCodeInvalidVpcEndpointServiceIdNotFound = "InvalidVpcEndpointServiceId.NotFound" + ErrCodeInvalidVpcIDNotFound = "InvalidVpcID.NotFound" + ErrCodeInvalidVpcPeeringConnectionIDNotFound = "InvalidVpcPeeringConnectionID.NotFound" + ErrCodeInvalidVpnConnectionIDNotFound = "InvalidVpnConnectionID.NotFound" + ErrCodeInvalidVpnGatewayAttachmentNotFound = "InvalidVpnGatewayAttachment.NotFound" + ErrCodeInvalidVpnGatewayIDNotFound = "InvalidVpnGatewayID.NotFound" + ErrCodeNatGatewayNotFound = "NatGatewayNotFound" + ErrCodeUnsupportedOperation = "UnsupportedOperation" ) func UnsuccessfulItemError(apiObject *ec2.UnsuccessfulItemError) error { diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index dc0a74b79d4..601b0338b76 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1438,93 +1438,6 @@ func FindSubnetIPv6CIDRBlockAssociationByID(conn *ec2.EC2, associationID string) return nil, &resource.NotFoundError{} } -func FindTransitGatewayPrefixListReference(conn *ec2.EC2, transitGatewayRouteTableID string, prefixListID string) (*ec2.TransitGatewayPrefixListReference, error) { - filters := map[string]string{ - "prefix-list-id": prefixListID, - } - - input := &ec2.GetTransitGatewayPrefixListReferencesInput{ - TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), - Filters: BuildAttributeFilterList(filters), - } - - var result *ec2.TransitGatewayPrefixListReference - - err := conn.GetTransitGatewayPrefixListReferencesPages(input, func(page *ec2.GetTransitGatewayPrefixListReferencesOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, transitGatewayPrefixListReference := range page.TransitGatewayPrefixListReferences { - if transitGatewayPrefixListReference == nil { - continue - } - - if aws.StringValue(transitGatewayPrefixListReference.PrefixListId) == prefixListID { - result = transitGatewayPrefixListReference - return false - } - } - - return !lastPage - }) - - return result, err -} - -func FindTransitGatewayPrefixListReferenceByID(conn *ec2.EC2, resourceID string) (*ec2.TransitGatewayPrefixListReference, error) { - transitGatewayRouteTableID, prefixListID, err := TransitGatewayPrefixListReferenceParseID(resourceID) - - if err != nil { - return nil, fmt.Errorf("error parsing EC2 Transit Gateway Prefix List Reference (%s) identifier: %w", resourceID, err) - } - - return FindTransitGatewayPrefixListReference(conn, transitGatewayRouteTableID, prefixListID) -} - -func FindTransitGatewayRouteTablePropagation(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) (*ec2.TransitGatewayRouteTablePropagation, error) { - if transitGatewayRouteTableID == "" { - return nil, nil - } - - input := &ec2.GetTransitGatewayRouteTablePropagationsInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("transit-gateway-attachment-id"), - Values: aws.StringSlice([]string{transitGatewayAttachmentID}), - }, - }, - TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), - } - - var result *ec2.TransitGatewayRouteTablePropagation - - err := conn.GetTransitGatewayRouteTablePropagationsPages(input, func(page *ec2.GetTransitGatewayRouteTablePropagationsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, transitGatewayRouteTablePropagation := range page.TransitGatewayRouteTablePropagations { - if transitGatewayRouteTablePropagation == nil { - continue - } - - if aws.StringValue(transitGatewayRouteTablePropagation.TransitGatewayAttachmentId) == transitGatewayAttachmentID { - result = transitGatewayRouteTablePropagation - return false - } - } - - return !lastPage - }) - - if err != nil { - return nil, err - } - - return result, nil -} - func FindVPCAttribute(conn *ec2.EC2, vpcID string, attribute string) (bool, error) { input := &ec2.DescribeVpcAttributeInput{ Attribute: aws.String(attribute), @@ -2147,6 +2060,83 @@ func FindVPNConnectionRouteByVPNConnectionIDAndCIDR(conn *ec2.EC2, vpnConnection } } +func FindTransitGateway(conn *ec2.EC2, input *ec2.DescribeTransitGatewaysInput) (*ec2.TransitGateway, error) { + output, err := FindTransitGateways(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 FindTransitGateways(conn *ec2.EC2, input *ec2.DescribeTransitGatewaysInput) ([]*ec2.TransitGateway, error) { + var output []*ec2.TransitGateway + + err := conn.DescribeTransitGatewaysPages(input, func(page *ec2.DescribeTransitGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGateways { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayByID(conn *ec2.EC2, id string) (*ec2.TransitGateway, error) { + input := &ec2.DescribeTransitGatewaysInput{ + TransitGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.TransitGatewayStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + func FindTransitGatewayAttachment(conn *ec2.EC2, input *ec2.DescribeTransitGatewayAttachmentsInput) (*ec2.TransitGatewayAttachment, error) { output, err := FindTransitGatewayAttachments(conn, input) @@ -2217,6 +2207,340 @@ func FindTransitGatewayAttachmentByID(conn *ec2.EC2, id string) (*ec2.TransitGat return output, nil } +func FindTransitGatewayMulticastDomain(conn *ec2.EC2, input *ec2.DescribeTransitGatewayMulticastDomainsInput) (*ec2.TransitGatewayMulticastDomain, error) { + output, err := FindTransitGatewayMulticastDomains(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Options == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayMulticastDomains(conn *ec2.EC2, input *ec2.DescribeTransitGatewayMulticastDomainsInput) ([]*ec2.TransitGatewayMulticastDomain, error) { + var output []*ec2.TransitGatewayMulticastDomain + + err := conn.DescribeTransitGatewayMulticastDomainsPages(input, func(page *ec2.DescribeTransitGatewayMulticastDomainsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayMulticastDomains { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayMulticastDomainByID(conn *ec2.EC2, id string) (*ec2.TransitGatewayMulticastDomain, error) { + input := &ec2.DescribeTransitGatewayMulticastDomainsInput{ + TransitGatewayMulticastDomainIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGatewayMulticastDomain(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.TransitGatewayMulticastDomainStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayMulticastDomainId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayMulticastDomainAssociation(conn *ec2.EC2, input *ec2.GetTransitGatewayMulticastDomainAssociationsInput) (*ec2.TransitGatewayMulticastDomainAssociation, error) { + output, err := FindTransitGatewayMulticastDomainAssociations(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Subnet == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayMulticastDomainAssociations(conn *ec2.EC2, input *ec2.GetTransitGatewayMulticastDomainAssociationsInput) ([]*ec2.TransitGatewayMulticastDomainAssociation, error) { + var output []*ec2.TransitGatewayMulticastDomainAssociation + + err := conn.GetTransitGatewayMulticastDomainAssociationsPages(input, func(page *ec2.GetTransitGatewayMulticastDomainAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.MulticastDomainAssociations { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayMulticastDomainAssociationByThreePartKey(conn *ec2.EC2, multicastDomainID, attachmentID, subnetID string) (*ec2.TransitGatewayMulticastDomainAssociation, error) { + input := &ec2.GetTransitGatewayMulticastDomainAssociationsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "subnet-id": subnetID, + "transit-gateway-attachment-id": attachmentID, + }), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + output, err := FindTransitGatewayMulticastDomainAssociation(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.Subnet.State); state == ec2.TransitGatewayMulitcastDomainAssociationStateDisassociated { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayAttachmentId) != attachmentID || aws.StringValue(output.Subnet.SubnetId) != subnetID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayMulticastGroups(conn *ec2.EC2, input *ec2.SearchTransitGatewayMulticastGroupsInput) ([]*ec2.TransitGatewayMulticastGroup, error) { + var output []*ec2.TransitGatewayMulticastGroup + + err := conn.SearchTransitGatewayMulticastGroupsPages(input, func(page *ec2.SearchTransitGatewayMulticastGroupsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.MulticastGroups { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayMulticastGroupMemberByThreePartKey(conn *ec2.EC2, multicastDomainID, groupIPAddress, eniID string) (*ec2.TransitGatewayMulticastGroup, error) { + input := &ec2.SearchTransitGatewayMulticastGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "group-ip-address": groupIPAddress, + "is-group-member": "true", + "is-group-source": "false", + }), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + output, err := FindTransitGatewayMulticastGroups(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, v := range output { + if aws.StringValue(v.NetworkInterfaceId) == eniID { + // Eventual consistency check. + if aws.StringValue(v.GroupIpAddress) != groupIPAddress || !aws.BoolValue(v.GroupMember) { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return v, nil + } + } + + return nil, tfresource.NewEmptyResultError(input) +} + +func FindTransitGatewayMulticastGroupSourceByThreePartKey(conn *ec2.EC2, multicastDomainID, groupIPAddress, eniID string) (*ec2.TransitGatewayMulticastGroup, error) { + input := &ec2.SearchTransitGatewayMulticastGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "group-ip-address": groupIPAddress, + "is-group-member": "false", + "is-group-source": "true", + }), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + output, err := FindTransitGatewayMulticastGroups(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, v := range output { + if aws.StringValue(v.NetworkInterfaceId) == eniID { + // Eventual consistency check. + if aws.StringValue(v.GroupIpAddress) != groupIPAddress || !aws.BoolValue(v.GroupSource) { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return v, nil + } + } + + return nil, tfresource.NewEmptyResultError(input) +} + +func FindTransitGatewayPrefixListReference(conn *ec2.EC2, input *ec2.GetTransitGatewayPrefixListReferencesInput) (*ec2.TransitGatewayPrefixListReference, error) { + output, err := FindTransitGatewayPrefixListReferences(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 FindTransitGatewayPrefixListReferences(conn *ec2.EC2, input *ec2.GetTransitGatewayPrefixListReferencesInput) ([]*ec2.TransitGatewayPrefixListReference, error) { + var output []*ec2.TransitGatewayPrefixListReference + + err := conn.GetTransitGatewayPrefixListReferencesPages(input, func(page *ec2.GetTransitGatewayPrefixListReferencesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayPrefixListReferences { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidRouteTableIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayPrefixListReferenceByTwoPartKey(conn *ec2.EC2, transitGatewayRouteTableID, prefixListID string) (*ec2.TransitGatewayPrefixListReference, error) { + input := &ec2.GetTransitGatewayPrefixListReferencesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "prefix-list-id": prefixListID, + }), + TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), + } + + output, err := FindTransitGatewayPrefixListReference(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.PrefixListId) != prefixListID || aws.StringValue(output.TransitGatewayRouteTableId) != transitGatewayRouteTableID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + func FindTransitGatewayRouteTables(conn *ec2.EC2, input *ec2.DescribeTransitGatewayRouteTablesInput) ([]*ec2.TransitGatewayRouteTable, error) { var output []*ec2.TransitGatewayRouteTable @@ -2248,6 +2572,49 @@ func FindTransitGatewayRouteTables(conn *ec2.EC2, input *ec2.DescribeTransitGate return output, nil } +func FindTransitGatewayRouteTablePropagation(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) (*ec2.TransitGatewayRouteTablePropagation, error) { + if transitGatewayRouteTableID == "" { + return nil, nil + } + + input := &ec2.GetTransitGatewayRouteTablePropagationsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("transit-gateway-attachment-id"), + Values: aws.StringSlice([]string{transitGatewayAttachmentID}), + }, + }, + TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), + } + + var result *ec2.TransitGatewayRouteTablePropagation + + err := conn.GetTransitGatewayRouteTablePropagationsPages(input, func(page *ec2.GetTransitGatewayRouteTablePropagationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, transitGatewayRouteTablePropagation := range page.TransitGatewayRouteTablePropagations { + if transitGatewayRouteTablePropagation == nil { + continue + } + + if aws.StringValue(transitGatewayRouteTablePropagation.TransitGatewayAttachmentId) == transitGatewayAttachmentID { + result = transitGatewayRouteTablePropagation + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return result, nil +} + func FindDHCPOptions(conn *ec2.EC2, input *ec2.DescribeDhcpOptionsInput) (*ec2.DhcpOptions, error) { output, err := FindDHCPOptionses(conn, input) diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index fb736cdcad9..1cff66b2adf 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -431,19 +431,67 @@ func StatusSubnetPrivateDNSHostnameTypeOnLaunch(conn *ec2.EC2, id string) resour } } -func StatusTransitGatewayPrefixListReferenceState(conn *ec2.EC2, transitGatewayRouteTableID string, prefixListID string) resource.StateRefreshFunc { +func StatusTransitGatewayState(conn *ec2.EC2, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - transitGatewayPrefixListReference, err := FindTransitGatewayPrefixListReference(conn, transitGatewayRouteTableID, prefixListID) + output, err := FindTransitGatewayByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } if err != nil { return nil, "", err } - if transitGatewayPrefixListReference == nil { + return output, aws.StringValue(output.State), nil + } +} + +func StatusTransitGatewayMulticastDomainState(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindTransitGatewayMulticastDomainByID(conn, id) + + if tfresource.NotFound(err) { return nil, "", nil } - return transitGatewayPrefixListReference, aws.StringValue(transitGatewayPrefixListReference.State), nil + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func StatusTransitGatewayMulticastDomainAssociationState(conn *ec2.EC2, multicastDomainID, attachmentID, subnetID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindTransitGatewayMulticastDomainAssociationByThreePartKey(conn, multicastDomainID, attachmentID, subnetID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Subnet.State), nil + } +} + +func StatusTransitGatewayPrefixListReferenceState(conn *ec2.EC2, transitGatewayRouteTableID string, prefixListID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindTransitGatewayPrefixListReferenceByTwoPartKey(conn, transitGatewayRouteTableID, prefixListID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil } } diff --git a/internal/service/ec2/sweep.go b/internal/service/ec2/sweep.go index 7d8f8da6a7b..c237a837e6a 100644 --- a/internal/service/ec2/sweep.go +++ b/internal/service/ec2/sweep.go @@ -224,6 +224,11 @@ func init() { F: sweepTransitGatewayPeeringAttachments, }) + resource.AddTestSweepers("aws_ec2_transit_gateway_multicast_domain", &resource.Sweeper{ + Name: "aws_ec2_transit_gateway_multicast_domain", + F: sweepTransitGatewayMulticastDomains, + }) + resource.AddTestSweepers("aws_ec2_transit_gateway", &resource.Sweeper{ Name: "aws_ec2_transit_gateway", F: sweepTransitGateways, @@ -238,6 +243,9 @@ func init() { resource.AddTestSweepers("aws_ec2_transit_gateway_vpc_attachment", &resource.Sweeper{ Name: "aws_ec2_transit_gateway_vpc_attachment", F: sweepTransitGatewayVPCAttachments, + Dependencies: []string{ + "aws_ec2_transit_gateway_multicast_domain", + }, }) resource.AddTestSweepers("aws_vpc_dhcp_options", &resource.Sweeper{ @@ -1574,6 +1582,49 @@ func sweepTransitGatewayPeeringAttachments(region string) error { return sweeperErrs.ErrorOrNil() } +func sweepTransitGatewayMulticastDomains(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*conns.AWSClient).EC2Conn + input := &ec2.DescribeTransitGatewayMulticastDomainsInput{} + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeTransitGatewayMulticastDomainsPages(input, func(page *ec2.DescribeTransitGatewayMulticastDomainsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayMulticastDomains { + r := ResourceTransitGatewayMulticastDomain() + d := r.Data(nil) + d.SetId(aws.StringValue(v.TransitGatewayMulticastDomainId)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping EC2 Transit Gateway Multicast Domain sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing EC2 Transit Gateway Multicast Domains (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping EC2 Transit Gateway Multicast Domains (%s): %w", region, err) + } + + return nil +} + func sweepTransitGateways(region string) error { client, err := sweep.SharedRegionalSweepClient(region) if err != nil { diff --git a/internal/service/ec2/transit_gateway.go b/internal/service/ec2/transit_gateway.go index 504dae02152..8046c8c7574 100644 --- a/internal/service/ec2/transit_gateway.go +++ b/internal/service/ec2/transit_gateway.go @@ -58,44 +58,39 @@ func ResourceTransitGateway() *schema.Resource { Computed: true, }, "auto_accept_shared_attachments": { - Type: schema.TypeString, - Optional: true, - Default: ec2.AutoAcceptSharedAttachmentsValueDisable, - ValidateFunc: validation.StringInSlice([]string{ - ec2.AutoAcceptSharedAttachmentsValueDisable, - ec2.AutoAcceptSharedAttachmentsValueEnable, - }, false), + Type: schema.TypeString, + Optional: true, + Default: ec2.AutoAcceptSharedAttachmentsValueDisable, + ValidateFunc: validation.StringInSlice(ec2.AutoAcceptSharedAttachmentsValue_Values(), false), }, "default_route_table_association": { - Type: schema.TypeString, - Optional: true, - Default: ec2.DefaultRouteTableAssociationValueEnable, - ValidateFunc: validation.StringInSlice([]string{ - ec2.DefaultRouteTableAssociationValueDisable, - ec2.DefaultRouteTableAssociationValueEnable, - }, false), + Type: schema.TypeString, + Optional: true, + Default: ec2.DefaultRouteTableAssociationValueEnable, + ValidateFunc: validation.StringInSlice(ec2.DefaultRouteTableAssociationValue_Values(), false), }, "default_route_table_propagation": { - Type: schema.TypeString, - Optional: true, - Default: ec2.DefaultRouteTablePropagationValueEnable, - ValidateFunc: validation.StringInSlice([]string{ - ec2.DefaultRouteTablePropagationValueDisable, - ec2.DefaultRouteTablePropagationValueEnable, - }, false), + Type: schema.TypeString, + Optional: true, + Default: ec2.DefaultRouteTablePropagationValueEnable, + ValidateFunc: validation.StringInSlice(ec2.DefaultRouteTablePropagationValue_Values(), false), }, "description": { Type: schema.TypeString, Optional: true, }, "dns_support": { - Type: schema.TypeString, - Optional: true, - Default: ec2.DnsSupportValueEnable, - ValidateFunc: validation.StringInSlice([]string{ - ec2.DnsSupportValueDisable, - ec2.DnsSupportValueEnable, - }, false), + Type: schema.TypeString, + Optional: true, + Default: ec2.DnsSupportValueEnable, + ValidateFunc: validation.StringInSlice(ec2.DnsSupportValue_Values(), false), + }, + "multicast_support": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.MulticastSupportValueDisable, + ValidateFunc: validation.StringInSlice(ec2.MulticastSupportValue_Values(), false), }, "owner_id": { Type: schema.TypeString, @@ -108,13 +103,10 @@ func ResourceTransitGateway() *schema.Resource { "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), "vpn_ecmp_support": { - Type: schema.TypeString, - Optional: true, - Default: ec2.VpnEcmpSupportValueEnable, - ValidateFunc: validation.StringInSlice([]string{ - ec2.VpnEcmpSupportValueDisable, - ec2.VpnEcmpSupportValueEnable, - }, false), + Type: schema.TypeString, + Optional: true, + Default: ec2.VpnEcmpSupportValueEnable, + ValidateFunc: validation.StringInSlice(ec2.VpnEcmpSupportValue_Values(), false), }, }, } @@ -131,6 +123,7 @@ func resourceTransitGatewayCreate(d *schema.ResourceData, meta interface{}) erro DefaultRouteTableAssociation: aws.String(d.Get("default_route_table_association").(string)), DefaultRouteTablePropagation: aws.String(d.Get("default_route_table_propagation").(string)), DnsSupport: aws.String(d.Get("dns_support").(string)), + MulticastSupport: aws.String(d.Get("multicast_support").(string)), VpnEcmpSupport: aws.String(d.Get("vpn_ecmp_support").(string)), }, TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeTransitGateway), @@ -200,6 +193,7 @@ func resourceTransitGatewayRead(d *schema.ResourceData, meta interface{}) error d.Set("default_route_table_propagation", transitGateway.Options.DefaultRouteTablePropagation) d.Set("description", transitGateway.Description) d.Set("dns_support", transitGateway.Options.DnsSupport) + d.Set("multicast_support", transitGateway.Options.MulticastSupport) d.Set("owner_id", transitGateway.OwnerId) d.Set("propagation_default_route_table_id", transitGateway.Options.PropagationDefaultRouteTableId) diff --git a/internal/service/ec2/transit_gateway_data_source.go b/internal/service/ec2/transit_gateway_data_source.go index 266a9520e41..0c8352c491a 100644 --- a/internal/service/ec2/transit_gateway_data_source.go +++ b/internal/service/ec2/transit_gateway_data_source.go @@ -55,6 +55,10 @@ func DataSourceTransitGateway() *schema.Resource { Optional: true, Computed: true, }, + "multicast_support": { + Type: schema.TypeString, + Computed: true, + }, "owner_id": { Type: schema.TypeString, Computed: true, @@ -119,6 +123,7 @@ func dataSourceTransitGatewayRead(d *schema.ResourceData, meta interface{}) erro d.Set("default_route_table_propagation", transitGateway.Options.DefaultRouteTablePropagation) d.Set("description", transitGateway.Description) d.Set("dns_support", transitGateway.Options.DnsSupport) + d.Set("multicast_support", transitGateway.Options.MulticastSupport) d.Set("owner_id", transitGateway.OwnerId) d.Set("propagation_default_route_table_id", transitGateway.Options.PropagationDefaultRouteTableId) diff --git a/internal/service/ec2/transit_gateway_data_source_test.go b/internal/service/ec2/transit_gateway_data_source_test.go index 8cafb8d3e57..ec71ab4e9cd 100644 --- a/internal/service/ec2/transit_gateway_data_source_test.go +++ b/internal/service/ec2/transit_gateway_data_source_test.go @@ -18,6 +18,10 @@ func TestAccEC2TransitGatewayDataSource_serial(t *testing.T) { "Filter": testAccTransitGatewayDataSource_Filter, "ID": testAccTransitGatewayDataSource_ID, }, + "MulticastDomain": { + "Filter": testAccTransitGatewayMulticastDomainDataSource_Filter, + "ID": testAccTransitGatewayMulticastDomainDataSource_ID, + }, "PeeringAttachment": { "FilterSameAccount": testAccTransitGatewayPeeringAttachmentDataSource_Filter_sameAccount, "FilterDifferentAccount": testAccTransitGatewayPeeringAttachmentDataSource_Filter_differentAccount, @@ -79,6 +83,7 @@ func testAccTransitGatewayDataSource_Filter(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "default_route_table_propagation", dataSourceName, "default_route_table_propagation"), resource.TestCheckResourceAttrPair(resourceName, "description", dataSourceName, "description"), resource.TestCheckResourceAttrPair(resourceName, "dns_support", dataSourceName, "dns_support"), + resource.TestCheckResourceAttrPair(resourceName, "multicast_support", dataSourceName, "multicast_support"), resource.TestCheckResourceAttrPair(resourceName, "owner_id", dataSourceName, "owner_id"), resource.TestCheckResourceAttrPair(resourceName, "propagation_default_route_table_id", dataSourceName, "propagation_default_route_table_id"), resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), diff --git a/internal/service/ec2/transit_gateway_multicast_domain.go b/internal/service/ec2/transit_gateway_multicast_domain.go new file mode 100644 index 00000000000..268b596f7e0 --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_domain.go @@ -0,0 +1,241 @@ +package ec2 + +import ( + "context" + "log" + "time" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "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" + 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" +) + +func ResourceTransitGatewayMulticastDomain() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTransitGatewayMulticastDomainCreate, + ReadWithoutTimeout: resourceTransitGatewayMulticastDomainRead, + UpdateWithoutTimeout: resourceTransitGatewayMulticastDomainUpdate, + DeleteWithoutTimeout: resourceTransitGatewayMulticastDomainDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "auto_accept_shared_associations": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.AutoAcceptSharedAssociationsValueDisable, + ValidateFunc: validation.StringInSlice(ec2.AutoAcceptSharedAssociationsValue_Values(), false), + }, + "igmpv2_support": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.Igmpv2SupportValueDisable, + ValidateFunc: validation.StringInSlice(ec2.Igmpv2SupportValue_Values(), false), + }, + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, + "static_sources_support": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ec2.StaticSourcesSupportValueDisable, + ValidateFunc: validation.StringInSlice(ec2.StaticSourcesSupportValue_Values(), false), + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "transit_gateway_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + }, + } +} + +func resourceTransitGatewayMulticastDomainCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + input := &ec2.CreateTransitGatewayMulticastDomainInput{ + Options: &ec2.CreateTransitGatewayMulticastDomainRequestOptions{ + AutoAcceptSharedAssociations: aws.String(d.Get("auto_accept_shared_associations").(string)), + Igmpv2Support: aws.String(d.Get("igmpv2_support").(string)), + StaticSourcesSupport: aws.String(d.Get("static_sources_support").(string)), + }, + TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeTransitGatewayMulticastDomain), + TransitGatewayId: aws.String(d.Get("transit_gateway_id").(string)), + } + + log.Printf("[DEBUG] Creating EC2 Transit Gateway Multicast Domain: %s", input) + output, err := conn.CreateTransitGatewayMulticastDomainWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating EC2 Transit Gateway Multicast Domain: %s", err) + } + + d.SetId(aws.StringValue(output.TransitGatewayMulticastDomain.TransitGatewayMulticastDomainId)) + + if _, err := WaitTransitGatewayMulticastDomainCreated(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for EC2 Transit Gateway Multicast Domain (%s) create: %s", d.Id(), err) + } + + return resourceTransitGatewayMulticastDomainRead(ctx, d, meta) +} + +func resourceTransitGatewayMulticastDomainRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + multicastDomain, err := FindTransitGatewayMulticastDomainByID(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Transit Gateway Multicast Domain %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading EC2 Transit Gateway Multicast Domain (%s): %s", d.Id(), err) + } + + d.Set("arn", multicastDomain.TransitGatewayMulticastDomainArn) + d.Set("auto_accept_shared_associations", multicastDomain.Options.AutoAcceptSharedAssociations) + d.Set("igmpv2_support", multicastDomain.Options.Igmpv2Support) + d.Set("owner_id", multicastDomain.OwnerId) + d.Set("static_sources_support", multicastDomain.Options.StaticSourcesSupport) + d.Set("transit_gateway_id", multicastDomain.TransitGatewayId) + + tags := KeyValueTags(multicastDomain.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceTransitGatewayMulticastDomainUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Id(), o, n); err != nil { + return diag.Errorf("error updating EC2 Transit Gateway Multicast Domain (%s) tags: %s", d.Id(), err) + } + } + + return resourceTransitGatewayMulticastDomainRead(ctx, d, meta) +} + +func resourceTransitGatewayMulticastDomainDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + groups, err := FindTransitGatewayMulticastGroups(conn, &ec2.SearchTransitGatewayMulticastGroupsInput{ + TransitGatewayMulticastDomainId: aws.String(d.Id()), + }) + + if tfresource.NotFound(err) { + err = nil + } + + if err != nil { + return diag.Errorf("error listing EC2 Transit Gateway Multicast Groups (%s): %s", d.Id(), err) + } + + var diags diag.Diagnostics + + for _, v := range groups { + if aws.BoolValue(v.GroupMember) { + err := deregisterTransitGatewayMulticastGroupMember(ctx, conn, d.Id(), aws.StringValue(v.GroupIpAddress), aws.StringValue(v.NetworkInterfaceId)) + + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } else if aws.BoolValue(v.GroupSource) { + err := deregisterTransitGatewayMulticastGroupSource(ctx, conn, d.Id(), aws.StringValue(v.GroupIpAddress), aws.StringValue(v.NetworkInterfaceId)) + + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } + } + + if diags.HasError() { + return diags + } + + associations, err := FindTransitGatewayMulticastDomainAssociations(conn, &ec2.GetTransitGatewayMulticastDomainAssociationsInput{ + TransitGatewayMulticastDomainId: aws.String(d.Id()), + }) + + if tfresource.NotFound(err) { + err = nil + } + + if err != nil { + return diag.Errorf("error listing EC2 Transit Gateway Multicast Domain Associations (%s): %s", d.Id(), err) + } + + for _, v := range associations { + err := disassociateTransitGatewayMulticastDomain(ctx, conn, d.Id(), aws.StringValue(v.TransitGatewayAttachmentId), aws.StringValue(v.Subnet.SubnetId), d.Timeout(schema.TimeoutDelete)) + + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } + + if diags.HasError() { + return diags + } + + log.Printf("[DEBUG] Deleting EC2 Transit Gateway Multicast Domain: %s", d.Id()) + _, err = conn.DeleteTransitGatewayMulticastDomainWithContext(ctx, &ec2.DeleteTransitGatewayMulticastDomainInput{ + TransitGatewayMulticastDomainId: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting EC2 Transit Gateway Multicast Domain (%s): %s", d.Id(), err) + } + + if _, err := WaitTransitGatewayMulticastDomainDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("error waiting for EC2 Transit Gateway Multicast Domain (%s) delete: %s", d.Id(), err) + } + + return nil +} diff --git a/internal/service/ec2/transit_gateway_multicast_domain_association.go b/internal/service/ec2/transit_gateway_multicast_domain_association.go new file mode 100644 index 00000000000..7dbb72c75ae --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_domain_association.go @@ -0,0 +1,167 @@ +package ec2 + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func ResourceTransitGatewayMulticastDomainAssociation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTransitGatewayMulticastDomainAssociationCreate, + ReadWithoutTimeout: resourceTransitGatewayMulticastDomainAssociationRead, + DeleteWithoutTimeout: resourceTransitGatewayMulticastDomainAssociationDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "transit_gateway_attachment_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "transit_gateway_multicast_domain_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceTransitGatewayMulticastDomainAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID := d.Get("transit_gateway_multicast_domain_id").(string) + attachmentID := d.Get("transit_gateway_attachment_id").(string) + subnetID := d.Get("subnet_id").(string) + id := TransitGatewayMulticastDomainAssociationCreateResourceID(multicastDomainID, attachmentID, subnetID) + input := &ec2.AssociateTransitGatewayMulticastDomainInput{ + SubnetIds: aws.StringSlice([]string{subnetID}), + TransitGatewayAttachmentId: aws.String(attachmentID), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + log.Printf("[DEBUG] Creating EC2 Transit Gateway Multicast Domain Association: %s", input) + _, err := conn.AssociateTransitGatewayMulticastDomainWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating EC2 Transit Gateway Multicast Domain Association (%s): %s", id, err) + } + + d.SetId(id) + + if _, err := WaitTransitGatewayMulticastDomainAssociationCreated(conn, multicastDomainID, attachmentID, subnetID, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for EC2 Transit Gateway Multicast Domain Association (%s) create: %s", d.Id(), err) + } + + return resourceTransitGatewayMulticastDomainAssociationRead(ctx, d, meta) +} + +func resourceTransitGatewayMulticastDomainAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID, attachmentID, subnetID, err := TransitGatewayMulticastDomainAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + multicastDomainAssociation, err := FindTransitGatewayMulticastDomainAssociationByThreePartKey(conn, multicastDomainID, attachmentID, subnetID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Transit Gateway Multicast Domain Association %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading EC2 Transit Gateway Multicast Domain Association (%s): %s", d.Id(), err) + } + + d.Set("subnet_id", multicastDomainAssociation.Subnet.SubnetId) + d.Set("transit_gateway_attachment_id", multicastDomainAssociation.TransitGatewayAttachmentId) + d.Set("transit_gateway_multicast_domain_id", multicastDomainID) + + return nil +} + +func resourceTransitGatewayMulticastDomainAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID, attachmentID, subnetID, err := TransitGatewayMulticastDomainAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + err = disassociateTransitGatewayMulticastDomain(ctx, conn, multicastDomainID, attachmentID, subnetID, d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func disassociateTransitGatewayMulticastDomain(ctx context.Context, conn *ec2.EC2, multicastDomainID, attachmentID, subnetID string, timeout time.Duration) error { + id := TransitGatewayMulticastDomainAssociationCreateResourceID(multicastDomainID, attachmentID, subnetID) + + log.Printf("[DEBUG] Deleting EC2 Transit Gateway Multicast Domain Association: %s", id) + _, err := conn.DisassociateTransitGatewayMulticastDomainWithContext(ctx, &ec2.DisassociateTransitGatewayMulticastDomainInput{ + SubnetIds: aws.StringSlice([]string{subnetID}), + TransitGatewayAttachmentId: aws.String(attachmentID), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Transit Gateway Multicast Domain Association (%s): %w", id, err) + } + + if _, err := WaitTransitGatewayMulticastDomainAssociationDeleted(conn, multicastDomainID, attachmentID, subnetID, timeout); err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Multicast Domain Association (%s) delete: %w", id, err) + } + + return nil +} + +const transitGatewayMulticastDomainAssociationIDSeparator = "/" + +func TransitGatewayMulticastDomainAssociationCreateResourceID(multicastDomainID, attachmentID, subnetID string) string { + parts := []string{multicastDomainID, attachmentID, subnetID} + id := strings.Join(parts, transitGatewayMulticastDomainAssociationIDSeparator) + + return id +} + +func TransitGatewayMulticastDomainAssociationParseResourceID(id string) (string, string, string, error) { + parts := strings.Split(id, transitGatewayMulticastDomainAssociationIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected MULTICAST-DOMAIN-ID%[2]sATTACHMENT-ID%[2]sSUBNET-ID", id, transitGatewayMulticastDomainAssociationIDSeparator) +} diff --git a/internal/service/ec2/transit_gateway_multicast_domain_association_test.go b/internal/service/ec2/transit_gateway_multicast_domain_association_test.go new file mode 100644 index 00000000000..cc0d96ab821 --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_domain_association_test.go @@ -0,0 +1,291 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "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 testAccTransitGatewayMulticastDomainAssociation_basic(t *testing.T) { + var v ec2.TransitGatewayMulticastDomainAssociation + resourceName := "aws_ec2_transit_gateway_multicast_domain_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainAssociationExists(resourceName, &v), + ), + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomainAssociation_disappears(t *testing.T) { + var v ec2.TransitGatewayMulticastDomainAssociation + resourceName := "aws_ec2_transit_gateway_multicast_domain_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainAssociationExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastDomainAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomainAssociation_Disappears_domain(t *testing.T) { + var v ec2.TransitGatewayMulticastDomainAssociation + resourceName := "aws_ec2_transit_gateway_multicast_domain_association.test" + domainResourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainAssociationExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastDomain(), domainResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomainAssociation_twoAssociations(t *testing.T) { + var v1, v2 ec2.TransitGatewayMulticastDomainAssociation + resource1Name := "aws_ec2_transit_gateway_multicast_domain_association.test1" + resource2Name := "aws_ec2_transit_gateway_multicast_domain_association.test2" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainAssociationTwoAssociationsConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainAssociationExists(resource1Name, &v1), + testAccCheckTransitGatewayMulticastDomainAssociationExists(resource2Name, &v2), + ), + }, + }, + }) +} + +func testAccCheckTransitGatewayMulticastDomainAssociationExists(n string, v *ec2.TransitGatewayMulticastDomainAssociation) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Transit Gateway Multicast Domain Association ID is set") + } + + multicastDomainID, attachmentID, subnetID, err := tfec2.TransitGatewayMulticastDomainAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + output, err := tfec2.FindTransitGatewayMulticastDomainAssociationByThreePartKey(conn, multicastDomainID, attachmentID, subnetID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckTransitGatewayMulticastDomainAssociationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_transit_gateway_multicast_domain_association" { + continue + } + + multicastDomainID, attachmentID, subnetID, err := tfec2.TransitGatewayMulticastDomainAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfec2.FindTransitGatewayMulticastDomainAssociationByThreePartKey(conn, multicastDomainID, attachmentID, subnetID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("EC2 Transit Gateway Multicast Domain Association %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccTransitGatewayMulticastDomainAssociationConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test" { + subnet_id = aws_subnet.test.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} +`, rName)) +} + +func testAccTransitGatewayMulticastDomainAssociationTwoAssociationsConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test1" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test2" { + availability_zone = data.aws_availability_zones.available.names[1] + cidr_block = "10.0.1.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test1" { + subnet_id = aws_subnet.test1.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test2" { + subnet_id = aws_subnet.test2.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} +`, rName)) +} diff --git a/internal/service/ec2/transit_gateway_multicast_domain_data_source.go b/internal/service/ec2/transit_gateway_multicast_domain_data_source.go new file mode 100644 index 00000000000..16c1fa2033d --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_domain_data_source.go @@ -0,0 +1,266 @@ +package ec2 + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func DataSourceTransitGatewayMulticastDomain() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceTransitGatewayMulticastDomainRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_id": { + Type: schema.TypeString, + Computed: true, + }, + "transit_gateway_attachment_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "auto_accept_shared_associations": { + Type: schema.TypeString, + Computed: true, + }, + "filter": DataSourceFiltersSchema(), + "igmpv2_support": { + Type: schema.TypeString, + Computed: true, + }, + "members": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group_ip_address": { + Type: schema.TypeString, + Computed: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, + "sources": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group_ip_address": { + Type: schema.TypeString, + Computed: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "static_sources_support": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + "transit_gateway_attachment_id": { + Type: schema.TypeString, + Computed: true, + }, + "transit_gateway_id": { + Type: schema.TypeString, + Computed: true, + }, + "transit_gateway_multicast_domain_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func dataSourceTransitGatewayMulticastDomainRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + input := &ec2.DescribeTransitGatewayMulticastDomainsInput{} + + if v, ok := d.GetOk("transit_gateway_multicast_domain_id"); ok { + input.TransitGatewayMulticastDomainIds = aws.StringSlice([]string{v.(string)}) + } + + input.Filters = append(input.Filters, BuildFiltersDataSource( + d.Get("filter").(*schema.Set), + )...) + + if len(input.Filters) == 0 { + input.Filters = nil + } + + transitGatewayMulticastDomain, err := FindTransitGatewayMulticastDomain(conn, input) + + if err != nil { + return diag.FromErr(tfresource.SingularDataSourceFindError("EC2 Transit Gateway Multicast Domain", err)) + } + + d.SetId(aws.StringValue(transitGatewayMulticastDomain.TransitGatewayMulticastDomainId)) + d.Set("arn", transitGatewayMulticastDomain.TransitGatewayMulticastDomainArn) + d.Set("auto_accept_shared_associations", transitGatewayMulticastDomain.Options.AutoAcceptSharedAssociations) + d.Set("igmpv2_support", transitGatewayMulticastDomain.Options.Igmpv2Support) + d.Set("owner_id", transitGatewayMulticastDomain.OwnerId) + d.Set("state", transitGatewayMulticastDomain.State) + d.Set("static_sources_support", transitGatewayMulticastDomain.Options.StaticSourcesSupport) + d.Set("transit_gateway_id", transitGatewayMulticastDomain.TransitGatewayId) + d.Set("transit_gateway_multicast_domain_id", transitGatewayMulticastDomain.TransitGatewayMulticastDomainId) + + if err := d.Set("tags", KeyValueTags(transitGatewayMulticastDomain.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + associations, err := FindTransitGatewayMulticastDomainAssociations(conn, &ec2.GetTransitGatewayMulticastDomainAssociationsInput{ + TransitGatewayMulticastDomainId: aws.String(d.Id()), + }) + + if err != nil { + return diag.Errorf("error listing EC2 Transit Gateway Multicast Domain Associations (%s): %s", d.Id(), err) + } + + if err := d.Set("associations", flattenTransitGatewayMulticastDomainAssociations(associations)); err != nil { + return diag.Errorf("error setting associations: %s", err) + } + + members, err := FindTransitGatewayMulticastGroups(conn, &ec2.SearchTransitGatewayMulticastGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "is-group-member": "true", + "is-group-source": "false", + }), + TransitGatewayMulticastDomainId: aws.String(d.Id()), + }) + + if err != nil { + return diag.Errorf("error listing EC2 Transit Gateway Multicast Group Members (%s): %s", d.Id(), err) + } + + if err := d.Set("members", flattenTransitGatewayMulticastGroups(members)); err != nil { + return diag.Errorf("error setting members: %s", err) + } + + sources, err := FindTransitGatewayMulticastGroups(conn, &ec2.SearchTransitGatewayMulticastGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "is-group-member": "false", + "is-group-source": "true", + }), + TransitGatewayMulticastDomainId: aws.String(d.Id()), + }) + + if err != nil { + return diag.Errorf("error listing EC2 Transit Gateway Multicast Group Members (%s): %s", d.Id(), err) + } + + if err := d.Set("sources", flattenTransitGatewayMulticastGroups(sources)); err != nil { + return diag.Errorf("error setting sources: %s", err) + } + + return nil +} + +func flattenTransitGatewayMulticastDomainAssociation(apiObject *ec2.TransitGatewayMulticastDomainAssociation) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Subnet.SubnetId; v != nil { + tfMap["subnet_id"] = aws.StringValue(v) + } + + if v := apiObject.TransitGatewayAttachmentId; v != nil { + tfMap["transit_gateway_attachment_id"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenTransitGatewayMulticastDomainAssociations(apiObjects []*ec2.TransitGatewayMulticastDomainAssociation) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenTransitGatewayMulticastDomainAssociation(apiObject)) + } + + return tfList +} + +func flattenTransitGatewayMulticastGroup(apiObject *ec2.TransitGatewayMulticastGroup) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.GroupIpAddress; v != nil { + tfMap["group_ip_address"] = aws.StringValue(v) + } + + if v := apiObject.NetworkInterfaceId; v != nil { + tfMap["network_interface_id"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenTransitGatewayMulticastGroups(apiObjects []*ec2.TransitGatewayMulticastGroup) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenTransitGatewayMulticastGroup(apiObject)) + } + + return tfList +} diff --git a/internal/service/ec2/transit_gateway_multicast_domain_data_source_test.go b/internal/service/ec2/transit_gateway_multicast_domain_data_source_test.go new file mode 100644 index 00000000000..c25711453a7 --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_domain_data_source_test.go @@ -0,0 +1,206 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "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-provider-aws/internal/acctest" +) + +func testAccTransitGatewayMulticastDomainDataSource_Filter(t *testing.T) { + dataSourceName := "data.aws_ec2_transit_gateway_multicast_domain.test" + resourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainFilterDataSourceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttr(dataSourceName, "associations.#", "0"), + resource.TestCheckResourceAttrPair(dataSourceName, "auto_accept_shared_associations", resourceName, "auto_accept_shared_associations"), + resource.TestCheckResourceAttrPair(dataSourceName, "igmpv2_support", resourceName, "igmpv2_support"), + resource.TestCheckResourceAttr(dataSourceName, "members.#", "0"), + resource.TestCheckResourceAttrPair(dataSourceName, "owner_id", resourceName, "owner_id"), + resource.TestCheckResourceAttr(dataSourceName, "sources.#", "0"), + resource.TestCheckResourceAttrPair(dataSourceName, "static_sources_support", resourceName, "static_sources_support"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "transit_gateway_id", resourceName, "transit_gateway_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "transit_gateway_multicast_domain_id", resourceName, "id"), + ), + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomainDataSource_ID(t *testing.T) { + dataSourceName := "data.aws_ec2_transit_gateway_multicast_domain.test" + resourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainIDDataSourceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttr(dataSourceName, "associations.#", "1"), + resource.TestCheckResourceAttrPair(dataSourceName, "auto_accept_shared_associations", resourceName, "auto_accept_shared_associations"), + resource.TestCheckResourceAttrPair(dataSourceName, "igmpv2_support", resourceName, "igmpv2_support"), + resource.TestCheckResourceAttr(dataSourceName, "members.#", "2"), + resource.TestCheckResourceAttrPair(dataSourceName, "owner_id", resourceName, "owner_id"), + resource.TestCheckResourceAttr(dataSourceName, "sources.#", "1"), + resource.TestCheckResourceAttrPair(dataSourceName, "static_sources_support", resourceName, "static_sources_support"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "transit_gateway_id", resourceName, "transit_gateway_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "transit_gateway_multicast_domain_id", resourceName, "id"), + ), + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomainFilterDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +data "aws_ec2_transit_gateway_multicast_domain" "test" { + filter { + name = "transit-gateway-multicast-domain-id" + values = [aws_ec2_transit_gateway_multicast_domain.test.id] + } +} +`, rName) +} + +func testAccTransitGatewayMulticastDomainIDDataSourceConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + static_sources_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test" { + subnet_id = aws_subnet.test.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} + +resource "aws_network_interface" "test3" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_group_source" "test" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test3.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} + +resource "aws_network_interface" "test1" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test2" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "test1" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test1.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "test2" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test2.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} + +data "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id + + depends_on = [ + aws_ec2_transit_gateway_multicast_group_member.test1, + aws_ec2_transit_gateway_multicast_group_member.test2, + aws_ec2_transit_gateway_multicast_group_source.test, + ] +} +`, rName)) +} diff --git a/internal/service/ec2/transit_gateway_multicast_domain_test.go b/internal/service/ec2/transit_gateway_multicast_domain_test.go new file mode 100644 index 00000000000..f4951fa323d --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_domain_test.go @@ -0,0 +1,272 @@ +package ec2_test + +import ( + "fmt" + "regexp" + "testing" + + "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 testAccTransitGatewayMulticastDomain_basic(t *testing.T) { + var v ec2.TransitGatewayMulticastDomain + resourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`transit-gateway-multicast-domain/.+`)), + resource.TestCheckResourceAttr(resourceName, "auto_accept_shared_associations", "disable"), + resource.TestCheckResourceAttr(resourceName, "igmpv2_support", "disable"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "static_sources_support", "disable"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "transit_gateway_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomain_disappears(t *testing.T) { + var v ec2.TransitGatewayMulticastDomain + resourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastDomain(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomain_tags(t *testing.T) { + var v ec2.TransitGatewayMulticastDomain + resourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := fmt.Sprintf("tf-testacc-tgwmulticast-%s", sdkacctest.RandString(8)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTransitGatewayMulticastDomainConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccTransitGatewayMulticastDomainConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccTransitGatewayMulticastDomain_igmpv2Support(t *testing.T) { + var v ec2.TransitGatewayMulticastDomain + resourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastDomainIGMPv2SupportConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastDomainExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "auto_accept_shared_associations", "enable"), + resource.TestCheckResourceAttr(resourceName, "igmpv2_support", "enable"), + resource.TestCheckResourceAttr(resourceName, "static_sources_support", "disable"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckTransitGatewayMulticastDomainExists(n string, v *ec2.TransitGatewayMulticastDomain) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Transit Gateway Multicast Domain ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + output, err := tfec2.FindTransitGatewayMulticastDomainByID(conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckTransitGatewayMulticastDomainDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_transit_gateway_multicast_domain" { + continue + } + + _, err := tfec2.FindTransitGatewayMulticastDomainByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("EC2 Transit Gateway Multicast Domain %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccTransitGatewayMulticastDomainConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id +} +`, rName) +} + +func testAccTransitGatewayMulticastDomainConfigTags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccTransitGatewayMulticastDomainConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccTransitGatewayMulticastDomainIGMPv2SupportConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + auto_accept_shared_associations = "enable" + igmpv2_support = "enable" + + tags = { + Name = %[1]q + } +} +`, rName) +} diff --git a/internal/service/ec2/transit_gateway_multicast_group_member.go b/internal/service/ec2/transit_gateway_multicast_group_member.go new file mode 100644 index 00000000000..9f6d0f0c976 --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_group_member.go @@ -0,0 +1,167 @@ +package ec2 + +import ( + "context" + "fmt" + "log" + "strings" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceTransitGatewayMulticastGroupMember() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTransitGatewayMulticastGroupMemberCreate, + ReadWithoutTimeout: resourceTransitGatewayMulticastGroupMemberRead, + DeleteWithoutTimeout: resourceTransitGatewayMulticastGroupMemberDelete, + + Schema: map[string]*schema.Schema{ + "group_ip_address": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidMulticastIPAddress, + }, + "network_interface_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "transit_gateway_multicast_domain_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceTransitGatewayMulticastGroupMemberCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID := d.Get("transit_gateway_multicast_domain_id").(string) + groupIPAddress := d.Get("group_ip_address").(string) + eniID := d.Get("network_interface_id").(string) + id := TransitGatewayMulticastGroupMemberCreateResourceID(multicastDomainID, groupIPAddress, eniID) + input := &ec2.RegisterTransitGatewayMulticastGroupMembersInput{ + GroupIpAddress: aws.String(groupIPAddress), + NetworkInterfaceIds: aws.StringSlice([]string{eniID}), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + log.Printf("[DEBUG] Creating EC2 Transit Gateway Multicast Group Member: %s", input) + _, err := conn.RegisterTransitGatewayMulticastGroupMembersWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating EC2 Transit Gateway Multicast Group Member (%s): %s", id, err) + } + + d.SetId(id) + + return resourceTransitGatewayMulticastGroupMemberRead(ctx, d, meta) +} + +func resourceTransitGatewayMulticastGroupMemberRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID, groupIPAddress, eniID, err := TransitGatewayMulticastGroupMemberParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + outputRaw, err := tfresource.RetryWhenNewResourceNotFoundContext(ctx, PropagationTimeout, func() (interface{}, error) { + return FindTransitGatewayMulticastGroupMemberByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + }, d.IsNewResource()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Transit Gateway Multicast Group Member %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading EC2 Transit Gateway Multicast Group Member (%s): %s", d.Id(), err) + } + + multicastGroup := outputRaw.(*ec2.TransitGatewayMulticastGroup) + + d.Set("group_ip_address", multicastGroup.GroupIpAddress) + d.Set("network_interface_id", multicastGroup.NetworkInterfaceId) + d.Set("transit_gateway_multicast_domain_id", multicastDomainID) + + return nil +} + +func resourceTransitGatewayMulticastGroupMemberDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID, groupIPAddress, eniID, err := TransitGatewayMulticastGroupMemberParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + err = deregisterTransitGatewayMulticastGroupMember(ctx, conn, multicastDomainID, groupIPAddress, eniID) + + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func deregisterTransitGatewayMulticastGroupMember(ctx context.Context, conn *ec2.EC2, multicastDomainID, groupIPAddress, eniID string) error { + id := TransitGatewayMulticastGroupMemberCreateResourceID(multicastDomainID, groupIPAddress, eniID) + + log.Printf("[DEBUG] Deleting EC2 Transit Gateway Multicast Group Member: %s", id) + _, err := conn.DeregisterTransitGatewayMulticastGroupMembersWithContext(ctx, &ec2.DeregisterTransitGatewayMulticastGroupMembersInput{ + GroupIpAddress: aws.String(groupIPAddress), + NetworkInterfaceIds: aws.StringSlice([]string{eniID}), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Transit Gateway Multicast Group Member (%s): %w", id, err) + } + + _, err = tfresource.RetryUntilNotFoundContext(ctx, PropagationTimeout, func() (interface{}, error) { + return FindTransitGatewayMulticastGroupMemberByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + }) + + if err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Multicast Group Member (%s) delete: %w", id, err) + } + + return nil +} + +const transitGatewayMulticastGroupMemberIDSeparator = "/" + +func TransitGatewayMulticastGroupMemberCreateResourceID(multicastDomainID, groupIPAddress, eniID string) string { + parts := []string{multicastDomainID, groupIPAddress, eniID} + id := strings.Join(parts, transitGatewayMulticastGroupMemberIDSeparator) + + return id +} + +func TransitGatewayMulticastGroupMemberParseResourceID(id string) (string, string, string, error) { + parts := strings.Split(id, transitGatewayMulticastGroupMemberIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected MULTICAST-DOMAIN-ID%[2]sGROUP-IP-ADDRESS%[2]sENI-ID", id, transitGatewayMulticastGroupMemberIDSeparator) +} diff --git a/internal/service/ec2/transit_gateway_multicast_group_member_test.go b/internal/service/ec2/transit_gateway_multicast_group_member_test.go new file mode 100644 index 00000000000..fbf4fda1eda --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_group_member_test.go @@ -0,0 +1,317 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "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 testAccTransitGatewayMulticastGroupMember_basic(t *testing.T) { + var v ec2.TransitGatewayMulticastGroup + resourceName := "aws_ec2_transit_gateway_multicast_group_member.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupMemberDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupMemberConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupMemberExists(resourceName, &v), + ), + }, + }, + }) +} + +func testAccTransitGatewayMulticastGroupMember_disappears(t *testing.T) { + var v ec2.TransitGatewayMulticastGroup + resourceName := "aws_ec2_transit_gateway_multicast_group_member.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupMemberDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupMemberConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupMemberExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastGroupMember(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastGroupMember_Disappears_domain(t *testing.T) { + var v ec2.TransitGatewayMulticastGroup + resourceName := "aws_ec2_transit_gateway_multicast_group_member.test" + domainResourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupMemberDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupMemberConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupMemberExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastDomain(), domainResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastGroupMember_twoMembers(t *testing.T) { + var v1, v2 ec2.TransitGatewayMulticastGroup + resource1Name := "aws_ec2_transit_gateway_multicast_group_member.test1" + resource2Name := "aws_ec2_transit_gateway_multicast_group_member.test2" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupMemberDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupMemberTwoMembersConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupMemberExists(resource1Name, &v1), + testAccCheckTransitGatewayMulticastGroupMemberExists(resource2Name, &v2), + ), + }, + }, + }) +} + +func testAccCheckTransitGatewayMulticastGroupMemberExists(n string, v *ec2.TransitGatewayMulticastGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Transit Gateway Multicast Group Member ID is set") + } + + multicastDomainID, groupIPAddress, eniID, err := tfec2.TransitGatewayMulticastGroupMemberParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + output, err := tfec2.FindTransitGatewayMulticastGroupMemberByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckTransitGatewayMulticastGroupMemberDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_transit_gateway_multicast_group_member" { + continue + } + + multicastDomainID, groupIPAddress, eniID, err := tfec2.TransitGatewayMulticastGroupMemberParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfec2.FindTransitGatewayMulticastGroupMemberByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("EC2 Transit Gateway Multicast Group Member %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccTransitGatewayMulticastGroupMemberConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test" { + subnet_id = aws_subnet.test.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} + +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "test" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} +`, rName)) +} + +func testAccTransitGatewayMulticastGroupMemberTwoMembersConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test" { + subnet_id = aws_subnet.test.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} + +resource "aws_network_interface" "test1" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test2" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "test1" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test1.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "test2" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test2.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} +`, rName)) +} diff --git a/internal/service/ec2/transit_gateway_multicast_group_source.go b/internal/service/ec2/transit_gateway_multicast_group_source.go new file mode 100644 index 00000000000..d12bcc3e467 --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_group_source.go @@ -0,0 +1,167 @@ +package ec2 + +import ( + "context" + "fmt" + "log" + "strings" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceTransitGatewayMulticastGroupSource() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTransitGatewayMulticastGroupSourceCreate, + ReadWithoutTimeout: resourceTransitGatewayMulticastGroupSourceRead, + DeleteWithoutTimeout: resourceTransitGatewayMulticastGroupSourceDelete, + + Schema: map[string]*schema.Schema{ + "group_ip_address": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidMulticastIPAddress, + }, + "network_interface_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "transit_gateway_multicast_domain_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceTransitGatewayMulticastGroupSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID := d.Get("transit_gateway_multicast_domain_id").(string) + groupIPAddress := d.Get("group_ip_address").(string) + eniID := d.Get("network_interface_id").(string) + id := TransitGatewayMulticastGroupSourceCreateResourceID(multicastDomainID, groupIPAddress, eniID) + input := &ec2.RegisterTransitGatewayMulticastGroupSourcesInput{ + GroupIpAddress: aws.String(groupIPAddress), + NetworkInterfaceIds: aws.StringSlice([]string{eniID}), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + log.Printf("[DEBUG] Creating EC2 Transit Gateway Multicast Group Source: %s", input) + _, err := conn.RegisterTransitGatewayMulticastGroupSourcesWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating EC2 Transit Gateway Multicast Group Source (%s): %s", id, err) + } + + d.SetId(id) + + return resourceTransitGatewayMulticastGroupSourceRead(ctx, d, meta) +} + +func resourceTransitGatewayMulticastGroupSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID, groupIPAddress, eniID, err := TransitGatewayMulticastGroupSourceParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + outputRaw, err := tfresource.RetryWhenNewResourceNotFoundContext(ctx, PropagationTimeout, func() (interface{}, error) { + return FindTransitGatewayMulticastGroupSourceByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + }, d.IsNewResource()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Transit Gateway Multicast Group Source %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading EC2 Transit Gateway Multicast Group Source (%s): %s", d.Id(), err) + } + + multicastGroup := outputRaw.(*ec2.TransitGatewayMulticastGroup) + + d.Set("group_ip_address", multicastGroup.GroupIpAddress) + d.Set("network_interface_id", multicastGroup.NetworkInterfaceId) + d.Set("transit_gateway_multicast_domain_id", multicastDomainID) + + return nil +} + +func resourceTransitGatewayMulticastGroupSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).EC2Conn + + multicastDomainID, groupIPAddress, eniID, err := TransitGatewayMulticastGroupSourceParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + err = deregisterTransitGatewayMulticastGroupSource(ctx, conn, multicastDomainID, groupIPAddress, eniID) + + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func deregisterTransitGatewayMulticastGroupSource(ctx context.Context, conn *ec2.EC2, multicastDomainID, groupIPAddress, eniID string) error { + id := TransitGatewayMulticastGroupSourceCreateResourceID(multicastDomainID, groupIPAddress, eniID) + + log.Printf("[DEBUG] Deleting EC2 Transit Gateway Multicast Group Source: %s", id) + _, err := conn.DeregisterTransitGatewayMulticastGroupSourcesWithContext(ctx, &ec2.DeregisterTransitGatewayMulticastGroupSourcesInput{ + GroupIpAddress: aws.String(groupIPAddress), + NetworkInterfaceIds: aws.StringSlice([]string{eniID}), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + }) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting EC2 Transit Gateway Multicast Group Source (%s): %w", id, err) + } + + _, err = tfresource.RetryUntilNotFoundContext(ctx, PropagationTimeout, func() (interface{}, error) { + return FindTransitGatewayMulticastGroupSourceByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + }) + + if err != nil { + return fmt.Errorf("error waiting for EC2 Transit Gateway Multicast Group Source (%s) delete: %w", id, err) + } + + return nil +} + +const transitGatewayMulticastGroupSourceIDSeparator = "/" + +func TransitGatewayMulticastGroupSourceCreateResourceID(multicastDomainID, groupIPAddress, eniID string) string { + parts := []string{multicastDomainID, groupIPAddress, eniID} + id := strings.Join(parts, transitGatewayMulticastGroupSourceIDSeparator) + + return id +} + +func TransitGatewayMulticastGroupSourceParseResourceID(id string) (string, string, string, error) { + parts := strings.Split(id, transitGatewayMulticastGroupSourceIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected MULTICAST-DOMAIN-ID%[2]sGROUP-IP-ADDRESS%[2]sENI-ID", id, transitGatewayMulticastGroupSourceIDSeparator) +} diff --git a/internal/service/ec2/transit_gateway_multicast_group_source_test.go b/internal/service/ec2/transit_gateway_multicast_group_source_test.go new file mode 100644 index 00000000000..b40c6294272 --- /dev/null +++ b/internal/service/ec2/transit_gateway_multicast_group_source_test.go @@ -0,0 +1,214 @@ +package ec2_test + +import ( + "fmt" + "testing" + + "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 testAccTransitGatewayMulticastGroupSource_basic(t *testing.T) { + var v ec2.TransitGatewayMulticastGroup + resourceName := "aws_ec2_transit_gateway_multicast_group_source.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupSourceExists(resourceName, &v), + ), + }, + }, + }) +} + +func testAccTransitGatewayMulticastGroupSource_disappears(t *testing.T) { + var v ec2.TransitGatewayMulticastGroup + resourceName := "aws_ec2_transit_gateway_multicast_group_source.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupSourceExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastGroupSource(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTransitGatewayMulticastGroupSource_Disappears_domain(t *testing.T) { + var v ec2.TransitGatewayMulticastGroup + resourceName := "aws_ec2_transit_gateway_multicast_group_source.test" + domainResourceName := "aws_ec2_transit_gateway_multicast_domain.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayMulticastGroupSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayMulticastGroupSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayMulticastGroupSourceExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayMulticastDomain(), domainResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckTransitGatewayMulticastGroupSourceExists(n string, v *ec2.TransitGatewayMulticastGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EC2 Transit Gateway Multicast Group Source ID is set") + } + + multicastDomainID, groupIPAddress, eniID, err := tfec2.TransitGatewayMulticastGroupSourceParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + output, err := tfec2.FindTransitGatewayMulticastGroupSourceByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckTransitGatewayMulticastGroupSourceDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_transit_gateway_multicast_group_source" { + continue + } + + multicastDomainID, groupIPAddress, eniID, err := tfec2.TransitGatewayMulticastGroupSourceParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfec2.FindTransitGatewayMulticastGroupSourceByThreePartKey(conn, multicastDomainID, groupIPAddress, eniID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("EC2 Transit Gateway Multicast Group Source %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccTransitGatewayMulticastGroupSourceConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + multicast_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + + static_sources_support = "enable" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "test" { + subnet_id = aws_subnet.test.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.test.id +} + +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_multicast_group_source" "test" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.test.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.test.transit_gateway_multicast_domain_id +} +`, rName)) +} diff --git a/internal/service/ec2/transit_gateway_prefix_list_reference.go b/internal/service/ec2/transit_gateway_prefix_list_reference.go index 7d14bba8f55..5b8790d7ff9 100644 --- a/internal/service/ec2/transit_gateway_prefix_list_reference.go +++ b/internal/service/ec2/transit_gateway_prefix_list_reference.go @@ -10,6 +10,7 @@ import ( "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/tfresource" ) func ResourceTransitGatewayPrefixListReference() *schema.Resource { @@ -95,40 +96,32 @@ func resourceTransitGatewayPrefixListReferenceCreate(d *schema.ResourceData, met func resourceTransitGatewayPrefixListReferenceRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - transitGatewayPrefixListReference, err := FindTransitGatewayPrefixListReferenceByID(conn, d.Id()) - - if tfawserr.ErrCodeEquals(err, ErrCodeInvalidRouteTableIDNotFound) { - log.Printf("[WARN] EC2 Transit Gateway Prefix List Reference (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } + transitGatewayRouteTableID, prefixListID, err := TransitGatewayPrefixListReferenceParseID(d.Id()) if err != nil { - return fmt.Errorf("error reading EC2 Transit Gateway Prefix List Reference (%s): %w", d.Id(), err) + return err } - if transitGatewayPrefixListReference == nil { + transitGatewayPrefixListReference, err := FindTransitGatewayPrefixListReferenceByTwoPartKey(conn, transitGatewayRouteTableID, prefixListID) + + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] EC2 Transit Gateway Prefix List Reference (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - if aws.StringValue(transitGatewayPrefixListReference.State) == ec2.TransitGatewayPrefixListReferenceStateDeleting { - log.Printf("[WARN] EC2 Transit Gateway Prefix List Reference (%s) deleting, removing from state", d.Id()) - d.SetId("") - return nil + if err != nil { + return fmt.Errorf("error reading EC2 Transit Gateway Prefix List Reference (%s): %w", d.Id(), err) } d.Set("blackhole", transitGatewayPrefixListReference.Blackhole) d.Set("prefix_list_id", transitGatewayPrefixListReference.PrefixListId) d.Set("prefix_list_owner_id", transitGatewayPrefixListReference.PrefixListOwnerId) - if transitGatewayPrefixListReference.TransitGatewayAttachment == nil { d.Set("transit_gateway_attachment_id", nil) } else { d.Set("transit_gateway_attachment_id", transitGatewayPrefixListReference.TransitGatewayAttachment.TransitGatewayAttachmentId) } - d.Set("transit_gateway_route_table_id", transitGatewayPrefixListReference.TransitGatewayRouteTableId) return nil @@ -178,7 +171,7 @@ func resourceTransitGatewayPrefixListReferenceDelete(d *schema.ResourceData, met transitGatewayRouteTableID, prefixListID, err := TransitGatewayPrefixListReferenceParseID(d.Id()) if err != nil { - return fmt.Errorf("error parsing EC2 Transit Gateway Prefix List Reference (%s) idenfitier: %w", d.Id(), err) + return err } input := &ec2.DeleteTransitGatewayPrefixListReferenceInput{ diff --git a/internal/service/ec2/transit_gateway_prefix_list_reference_test.go b/internal/service/ec2/transit_gateway_prefix_list_reference_test.go index 5e3b35b6199..ae67fc02e55 100644 --- a/internal/service/ec2/transit_gateway_prefix_list_reference_test.go +++ b/internal/service/ec2/transit_gateway_prefix_list_reference_test.go @@ -5,13 +5,13 @@ import ( "testing" "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 testAccTransitGatewayPrefixListReference_basic(t *testing.T) { @@ -152,46 +152,51 @@ func testAccCheckTransitGatewayPrefixListReferenceDestroy(s *terraform.State) er continue } - transitGatewayPrefixListReference, err := tfec2.FindTransitGatewayPrefixListReferenceByID(conn, rs.Primary.ID) + transitGatewayRouteTableID, prefixListID, err := tfec2.TransitGatewayPrefixListReferenceParseID(rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidRouteTableIDNotFound) { + if err != nil { + return err + } + + _, err = tfec2.FindTransitGatewayPrefixListReferenceByTwoPartKey(conn, transitGatewayRouteTableID, prefixListID) + + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("error reading EC2 Transit Gateway Prefix List Reference (%s): %w", rs.Primary.ID, err) + return err } - if transitGatewayPrefixListReference != nil { - return fmt.Errorf("EC2 Transit Gateway Prefix List Reference (%s) still exists", rs.Primary.ID) - } + return fmt.Errorf("EC2 Transit Gateway Prefix List Reference %s still exists", rs.Primary.ID) } return nil } -func testAccTransitGatewayPrefixListReferenceExists(resourceName string) resource.TestCheckFunc { +func testAccTransitGatewayPrefixListReferenceExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] - + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("resource %s not found", resourceName) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("resource %s has not set its id", resourceName) + return fmt.Errorf("No EC2 Transit Gateway Prefix List Reference is set") } - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - transitGatewayPrefixListReference, err := tfec2.FindTransitGatewayPrefixListReferenceByID(conn, rs.Primary.ID) + transitGatewayRouteTableID, prefixListID, err := tfec2.TransitGatewayPrefixListReferenceParseID(rs.Primary.ID) if err != nil { - return fmt.Errorf("error reading EC2 Transit Gateway Prefix List Reference (%s): %w", rs.Primary.ID, err) + return err } - if transitGatewayPrefixListReference == nil { - return fmt.Errorf("EC2 Transit Gateway Prefix List Reference (%s) not found", rs.Primary.ID) + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + + _, err = tfec2.FindTransitGatewayPrefixListReferenceByTwoPartKey(conn, transitGatewayRouteTableID, prefixListID) + + if err != nil { + return err } return nil diff --git a/internal/service/ec2/transit_gateway_test.go b/internal/service/ec2/transit_gateway_test.go index 7eb1664eea7..bb6471bcddf 100644 --- a/internal/service/ec2/transit_gateway_test.go +++ b/internal/service/ec2/transit_gateway_test.go @@ -31,6 +31,29 @@ func TestAccEC2TransitGateway_serial(t *testing.T) { "Tags": testAccTransitGateway_Tags, "VpnEcmpSupport": testAccTransitGateway_VPNECMPSupport, }, + "MulticastDomain": { + "basic": testAccTransitGatewayMulticastDomain_basic, + "disappears": testAccTransitGatewayMulticastDomain_disappears, + "tags": testAccTransitGatewayMulticastDomain_tags, + "IGMPv2Support": testAccTransitGatewayMulticastDomain_igmpv2Support, + }, + "MulticastDomainAssociation": { + "basic": testAccTransitGatewayMulticastDomainAssociation_basic, + "disappears": testAccTransitGatewayMulticastDomainAssociation_disappears, + "DomainDisappears": testAccTransitGatewayMulticastDomainAssociation_Disappears_domain, + "TwoAssociations": testAccTransitGatewayMulticastDomainAssociation_twoAssociations, + }, + "MulticastGroupMember": { + "basic": testAccTransitGatewayMulticastGroupMember_basic, + "disappears": testAccTransitGatewayMulticastGroupMember_disappears, + "DomainDisappears": testAccTransitGatewayMulticastGroupMember_Disappears_domain, + "TwoMembers": testAccTransitGatewayMulticastGroupMember_twoMembers, + }, + "MulticastGroupSource": { + "basic": testAccTransitGatewayMulticastGroupSource_basic, + "disappears": testAccTransitGatewayMulticastGroupSource_disappears, + "DomainDisappears": testAccTransitGatewayMulticastGroupSource_Disappears_domain, + }, "PeeringAttachment": { "basic": testAccTransitGatewayPeeringAttachment_basic, "disappears": testAccTransitGatewayPeeringAttachment_disappears, @@ -122,6 +145,7 @@ func testAccTransitGateway_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "default_route_table_propagation", ec2.DefaultRouteTablePropagationValueEnable), resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueEnable), + resource.TestCheckResourceAttr(resourceName, "multicast_support", ec2.MulticastSupportValueDisable), acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttrSet(resourceName, "propagation_default_route_table_id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index f5a0ba4a68d..993cd71ebba 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -672,6 +672,74 @@ func WaitSubnetPrivateDNSHostnameTypeOnLaunchUpdated(conn *ec2.EC2, subnetID str return nil, err } +func WaitTransitGatewayMulticastDomainCreated(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.TransitGatewayMulticastDomain, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.TransitGatewayMulticastDomainStatePending}, + Target: []string{ec2.TransitGatewayMulticastDomainStateAvailable}, + Refresh: StatusTransitGatewayMulticastDomainState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.TransitGatewayMulticastDomain); ok { + return output, err + } + + return nil, err +} + +func WaitTransitGatewayMulticastDomainDeleted(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.TransitGatewayMulticastDomain, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.TransitGatewayMulticastDomainStateAvailable, ec2.TransitGatewayMulticastDomainStateDeleting}, + Target: []string{}, + Refresh: StatusTransitGatewayMulticastDomainState(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.TransitGatewayMulticastDomain); ok { + return output, err + } + + return nil, err +} + +func WaitTransitGatewayMulticastDomainAssociationCreated(conn *ec2.EC2, multicastDomainID, attachmentID, subnetID string, timeout time.Duration) (*ec2.TransitGatewayMulticastDomainAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeAssociating}, + Target: []string{ec2.AssociationStatusCodeAssociated}, + Refresh: StatusTransitGatewayMulticastDomainAssociationState(conn, multicastDomainID, attachmentID, subnetID), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.TransitGatewayMulticastDomainAssociation); ok { + return output, err + } + + return nil, err +} + +func WaitTransitGatewayMulticastDomainAssociationDeleted(conn *ec2.EC2, multicastDomainID, attachmentID, subnetID string, timeout time.Duration) (*ec2.TransitGatewayMulticastDomainAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AssociationStatusCodeAssociated, ec2.AssociationStatusCodeDisassociating}, + Target: []string{}, + Refresh: StatusTransitGatewayMulticastDomainAssociationState(conn, multicastDomainID, attachmentID, subnetID), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.TransitGatewayMulticastDomainAssociation); ok { + return output, err + } + + return nil, err +} + const ( TransitGatewayPrefixListReferenceTimeout = 5 * time.Minute ) @@ -703,10 +771,6 @@ func WaitTransitGatewayPrefixListReferenceStateDeleted(conn *ec2.EC2, transitGat outputRaw, err := stateConf.WaitForState() - if tfawserr.ErrCodeEquals(err, ErrCodeInvalidRouteTableIDNotFound) { - return nil, nil - } - if output, ok := outputRaw.(*ec2.TransitGatewayPrefixListReference); ok { return output, err } diff --git a/internal/tfresource/retry.go b/internal/tfresource/retry.go index 1a55b379ffc..9042a3ce128 100644 --- a/internal/tfresource/retry.go +++ b/internal/tfresource/retry.go @@ -2,6 +2,7 @@ package tfresource import ( "context" + "errors" "math/rand" "sync" "time" @@ -23,9 +24,10 @@ func RetryWhenContext(ctx context.Context, timeout time.Duration, f func() (inte err := resource.Retry(timeout, func() *resource.RetryError { // nosemgrep: helper-schema-resource-Retry-without-TimeoutError-check var err error + var retry bool output, err = f() - retry, err := retryable(err) + retry, err = retryable(err) if retry { return resource.RetryableError(err) @@ -71,6 +73,28 @@ func RetryWhenAWSErrCodeEquals(timeout time.Duration, f func() (interface{}, err return RetryWhenAWSErrCodeEqualsContext(context.Background(), timeout, f, codes...) } +var resourceFoundError = errors.New(`found resource`) + +// RetryUntilNotFoundContext retries the specified function until it returns a resource.NotFoundError. +func RetryUntilNotFoundContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { + return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { + if NotFound(err) { + return false, nil + } + + if err != nil { + return false, err + } + + return true, resourceFoundError + }) +} + +// RetryUntilNotFound retries the specified function until it returns a resource.NotFoundError. +func RetryUntilNotFound(timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { + return RetryUntilNotFoundContext(context.Background(), timeout, f) +} + // RetryWhenNotFoundContext retries the specified function when it returns a resource.NotFoundError. func RetryWhenNotFoundContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { diff --git a/internal/tfresource/retry_test.go b/internal/tfresource/retry_test.go index 90f2bb53200..eb8d7049e36 100644 --- a/internal/tfresource/retry_test.go +++ b/internal/tfresource/retry_test.go @@ -224,6 +224,68 @@ func TestRetryWhenNotFound(t *testing.T) { } } +func TestRetryUntilNotFound(t *testing.T) { + var retryCount int32 + + testCases := []struct { + Name string + F func() (interface{}, error) + ExpectError bool + }{ + { + Name: "no error", + F: func() (interface{}, error) { + return nil, nil + }, + ExpectError: true, + }, + { + Name: "other error", + F: func() (interface{}, error) { + return nil, errors.New("TestCode") + }, + ExpectError: true, + }, + { + Name: "AWS error", + F: func() (interface{}, error) { + return nil, awserr.New("Testing", "Testing", nil) + }, + ExpectError: true, + }, + { + Name: "NotFoundError", + F: func() (interface{}, error) { + return nil, &resource.NotFoundError{} + }, + }, + { + Name: "retryable NotFoundError", + F: func() (interface{}, error) { + if atomic.CompareAndSwapInt32(&retryCount, 0, 1) { + return nil, nil + } + + return nil, &resource.NotFoundError{} + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + retryCount = 0 + + _, err := tfresource.RetryUntilNotFound(5*time.Second, testCase.F) + + if testCase.ExpectError && err == nil { + t.Fatal("expected error") + } else if !testCase.ExpectError && err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + } +} + func TestRetryConfigContext_error(t *testing.T) { t.Parallel() diff --git a/internal/verify/validate.go b/internal/verify/validate.go index b85c51f9b46..ca63b1867a4 100644 --- a/internal/verify/validate.go +++ b/internal/verify/validate.go @@ -203,6 +203,29 @@ func ValidLaunchTemplateName(v interface{}, k string) (ws []string, errors []err return } +// validateMulticastIPAddress validates that the specified string is a multicast IP address. +func validateMulticastIPAddress(s string) error { + ip := net.ParseIP(s) + if ip == nil { + return fmt.Errorf("%q is not a valid IP address", s) + } + + if !ip.IsMulticast() { + return fmt.Errorf("%q is not a valid multicast address", s) + } + + return nil +} + +func ValidMulticastIPAddress(v interface{}, k string) (ws []string, errors []error) { + if err := validateMulticastIPAddress(v.(string)); err != nil { + errors = append(errors, err) + return + } + + return +} + func ValidOnceADayWindowFormat(v interface{}, k string) (ws []string, errors []error) { // valid time format is "hh24:mi" validTimeFormat := "([0-1][0-9]|2[0-3]):([0-5][0-9])" diff --git a/website/docs/d/ec2_transit_gateway.html.markdown b/website/docs/d/ec2_transit_gateway.html.markdown index fd27295078b..02e958a49dd 100644 --- a/website/docs/d/ec2_transit_gateway.html.markdown +++ b/website/docs/d/ec2_transit_gateway.html.markdown @@ -55,6 +55,7 @@ In addition to all arguments above, the following attributes are exported: * `default_route_table_propagation` - Whether resource attachments automatically propagate routes to the default propagation route table. * `description` - Description of the EC2 Transit Gateway * `dns_support` - Whether DNS support is enabled. +* `multicast_support` - (Optional) Whether Multicast support is enabled. Required to use `ec2_transit_gateway_multicast_domain`. Valid values: `disable`, `enable`. Default value: `disable`. * `id` - EC2 Transit Gateway identifier * `owner_id` - Identifier of the AWS account that owns the EC2 Transit Gateway * `propagation_default_route_table_id` - Identifier of the default propagation route table. diff --git a/website/docs/d/ec2_transit_gateway_multicast_domain.html.markdown b/website/docs/d/ec2_transit_gateway_multicast_domain.html.markdown new file mode 100644 index 00000000000..98e56e89f48 --- /dev/null +++ b/website/docs/d/ec2_transit_gateway_multicast_domain.html.markdown @@ -0,0 +1,70 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_multicast_domain" +description: |- + Get information on an EC2 Transit Gateway Multicast Domain +--- + +# Data Source: aws_ec2_transit_gateway_multicast_domain + +Get information on an EC2 Transit Gateway Multicast Domain. + +## Example Usage + +### By Filter + +```terraform +data "aws_ec2_transit_gateway_multicast_domain" "example" { + filter { + name = "transit-gateway-multicast-domain-id" + values = ["tgw-mcast-domain-12345678"] + } +} +``` + +### By Identifier + +```terraform +data "aws_ec2_transit_gateway_multicast_domain" "example" { + transit_gateway_multicast_domain_id = "tgw-mcast-domain-12345678" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `filter` - (Optional) One or more configuration blocks containing name-values filters. Detailed below. +* `transit_gateway_multicast_domain_id` - (Optional) Identifier of the EC2 Transit Gateway Multicast Domain. + +### filter Argument Reference + +This block allows for complex filters. You can use one or more `filter` blocks. + +The following arguments are required: + +* `name` - (Required) The name of the field to filter by, as defined by [the underlying AWS API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeTransitGatewayMulticastDomains.html). +* `values` - (Required) Set of values that are accepted for the given field. A multicast domain will be selected if any one of the given values matches. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Transit Gateway Multicast Domain identifier. +* `arn` - EC2 Transit Gateway Multicast Domain Amazon Resource Name (ARN). +* `associations` - EC2 Transit Gateway Multicast Domain Associations + * `subnet_id` - The ID of the subnet associated with the transit gateway multicast domain. + * `transit_gateway_attachment_id` - The ID of the transit gateway attachment. +* `auto_accept_shared_associations` - Whether to automatically accept cross-account subnet associations that are associated with the EC2 Transit Gateway Multicast Domain. +* `igmpv2_support` - Whether to enable Internet Group Management Protocol (IGMP) version 2 for the EC2 Transit Gateway Multicast Domain. +* `members` - EC2 Multicast Domain Group Members + * `group_ip_address` - The IP address assigned to the transit gateway multicast group. + * `network_interface_id` - The group members' network interface ID. +* `owner_id` - Identifier of the AWS account that owns the EC2 Transit Gateway Multicast Domain. +* `sources` - EC2 Multicast Domain Group Sources + * `group_ip_address` - The IP address assigned to the transit gateway multicast group. + * `network_interface_id` - The group members' network interface ID. +* `static_sources_support` - Whether to enable support for statically configuring multicast group sources for the EC2 Transit Gateway Multicast Domain. +* `tags` - Key-value tags for the EC2 Transit Gateway Multicast Domain. +* `transit_gateway_id` - EC2 Transit Gateway identifier. diff --git a/website/docs/r/ec2_transit_gateway_multicast_domain.html.markdown b/website/docs/r/ec2_transit_gateway_multicast_domain.html.markdown new file mode 100644 index 00000000000..45f83f662ac --- /dev/null +++ b/website/docs/r/ec2_transit_gateway_multicast_domain.html.markdown @@ -0,0 +1,178 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_multicast_domain" +description: |- + Manages an EC2 Transit Gateway Multicast Domain +--- + +# Resource: aws_ec2_transit_gateway_multicast_domain + +Manages an EC2 Transit Gateway Multicast Domain. + +## Example Usage + +```terraform +data "aws_availability_zones" "available" { + state = "available" +} + +data "aws_ami" "amazon_linux" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = [ + "amzn-ami-hvm-*-x86_64-gp2", + ] + } + + filter { + name = "owner-alias" + values = [ + "amazon", + ] + } +} + +resource "aws_vpc" "vpc1" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_vpc" "vpc2" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "subnet1" { + vpc_id = aws_vpc.vpc1.id + cidr_block = "10.0.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] +} + +resource "aws_subnet" "subnet2" { + vpc_id = aws_vpc.vpc1.id + cidr_block = "10.0.2.0/24" + availability_zone = data.aws_availability_zones.available.names[1] +} + +resource "aws_subnet" "subnet3" { + vpc_id = aws_vpc.vpc2.id + cidr_block = "10.1.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] +} + +resource "aws_instance" "instance1" { + ami = data.aws_ami.amazon_linux.id + instance_type = "t2.micro" + subnet_id = aws_subnet.subnet1.id +} + +resource "aws_instance" "instance2" { + ami = data.aws_ami.amazon_linux.id + instance_type = "t2.micro" + subnet_id = aws_subnet.subnet2.id +} + +resource "aws_instance" "instance3" { + ami = data.aws_ami.amazon_linux.id + instance_type = "t2.micro" + subnet_id = aws_subnet.subnet3.id +} + +resource "aws_ec2_transit_gateway" "tgw" { + multicast_support = "enable" +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "attachment1" { + subnet_ids = [aws_subnet.subnet1.id, aws_subnet.subnet2.id] + transit_gateway_id = aws_ec2_transit_gateway.tgw.id + vpc_id = aws_vpc.vpc1.id +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "attachment2" { + subnet_ids = [aws_subnet.subnet3.id] + transit_gateway_id = aws_ec2_transit_gateway.tgw.id + vpc_id = aws_vpc.vpc2.id +} + +resource "aws_ec2_transit_gateway_multicast_domain" "domain" { + transit_gateway_id = aws_ec2_transit_gateway.tgw.id + + static_sources_support = "enable" + + tags = { + Name = "Transit_Gateway_Multicast_Domain_Example" + } +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "association3" { + subnet_id = aws_subnet.subnet3.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.attachment2.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.domain.id +} + +resource "aws_ec2_transit_gateway_multicast_group_source" "source" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_instance.instance3.primary_network_interface_id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.association3.transit_gateway_multicast_domain_id +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "association1" { + subnet_id = aws_subnet.subnet1.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.attachment1.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.domain.id +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "association2" { + subnet_id = aws_subnet.subnet2.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.attachment2.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.domain.id +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "member1" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_instance.instance1.primary_network_interface_id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.association1.transit_gateway_multicast_domain_id +} + +resource "aws_ec2_transit_gateway_multicast_group_member" "member2" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_instance.instance2.primary_network_interface_id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain_association.association1.transit_gateway_multicast_domain_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `transit_gateway_id` - (Required) EC2 Transit Gateway identifier. The EC2 Transit Gateway must have `multicast_support` enabled. +* `auto_accept_shared_associations` - (Optional) Whether to automatically accept cross-account subnet associations that are associated with the EC2 Transit Gateway Multicast Domain. Valid values: `disable`, `enable`. Default value: `disable`. +* `igmpv2_support` - (Optional) Whether to enable Internet Group Management Protocol (IGMP) version 2 for the EC2 Transit Gateway Multicast Domain. Valid values: `disable`, `enable`. Default value: `disable`. +* `static_sources_support` - (Optional) Whether to enable support for statically configuring multicast group sources for the EC2 Transit Gateway Multicast Domain. Valid values: `disable`, `enable`. Default value: `disable`. +* `tags` - (Optional) Key-value tags for the EC2 Transit Gateway Multicast Domain. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Transit Gateway Multicast Domain identifier. +* `arn` - EC2 Transit Gateway Multicast Domain Amazon Resource Name (ARN). +* `owner_id` - Identifier of the AWS account that owns the EC2 Transit Gateway Multicast Domain. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Timeouts + +`aws_ec2_transit_gateway_multicast_domain` provides the following [Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: + +- `create` - (Default `10 minutes`) Used for multicast domain creation +- `delete` - (Default `10 minutes`) Used for multicast domain deletion + +## Import + +`aws_ec2_transit_gateway_multicast_domain` can be imported by using the EC2 Transit Gateway Multicast Domain identifier, e.g., + +``` +terraform import aws_ec2_transit_gateway_multicast_domain.example tgw-mcast-domain-12345 +``` \ No newline at end of file diff --git a/website/docs/r/ec2_transit_gateway_multicast_domain_association.html.markdown b/website/docs/r/ec2_transit_gateway_multicast_domain_association.html.markdown new file mode 100644 index 00000000000..9b18c7ba888 --- /dev/null +++ b/website/docs/r/ec2_transit_gateway_multicast_domain_association.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_multicast_domain_association" +description: |- + Manages an EC2 Transit Gateway Multicast Domain Association +--- + +# Resource: aws_ec2_transit_gateway_multicast_domain_association + +Associates the specified subnet and transit gateway attachment with the specified transit gateway multicast domain. + +## Example Usage + +```terraform +resource "aws_ec2_transit_gateway" "example" { + multicast_support = "enable" +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "example" { + subnet_ids = [aws_subnet.example.id] + transit_gateway_id = aws_ec2_transit_gateway.example.id + vpc_id = aws_vpc.example.id +} + +resource "aws_ec2_transit_gateway_multicast_domain" "example" { + transit_gateway_id = aws_ec2_transit_gateway.example.id +} + +resource "aws_ec2_transit_gateway_multicast_domain_association" "example" { + subnet_id = aws_subnet.example.id + transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.example.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `subnet_id` - (Required) The ID of the subnet to associate with the transit gateway multicast domain. +* `transit_gateway_attachment_id` - (Required) The ID of the transit gateway attachment. +* `transit_gateway_multicast_domain_id` - (Required) The ID of the transit gateway multicast domain. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Transit Gateway Multicast Domain Association identifier. + +## Timeouts + +`aws_ec2_transit_gateway_multicast_domain_association` provides the following [Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: + +- `create` - (Default `10 minutes`) Used for multicast domain association creation +- `delete` - (Default `10 minutes`) Used for multicast domain association deletion diff --git a/website/docs/r/ec2_transit_gateway_multicast_group_member.html.markdown b/website/docs/r/ec2_transit_gateway_multicast_group_member.html.markdown new file mode 100644 index 00000000000..618a1885017 --- /dev/null +++ b/website/docs/r/ec2_transit_gateway_multicast_group_member.html.markdown @@ -0,0 +1,36 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_multicast_group_member" +description: |- + Manages an EC2 Transit Gateway Multicast Group Member +--- + +# Resource: aws_ec2_transit_gateway_multicast_group_member + +Registers members (network interfaces) with the transit gateway multicast group. +A member is a network interface associated with a supported EC2 instance that receives multicast traffic. + +## Example Usage + +```terraform +resource "aws_ec2_transit_gateway_multicast_group_member" "example" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.example.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `group_ip_address` - (Required) The IP address assigned to the transit gateway multicast group. +* `network_interface_id` - (Required) The group members' network interface ID to register with the transit gateway multicast group. +* `transit_gateway_multicast_domain_id` - (Required) The ID of the transit gateway multicast domain. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Transit Gateway Multicast Group Member identifier. diff --git a/website/docs/r/ec2_transit_gateway_multicast_group_source.html.markdown b/website/docs/r/ec2_transit_gateway_multicast_group_source.html.markdown new file mode 100644 index 00000000000..70adb645d78 --- /dev/null +++ b/website/docs/r/ec2_transit_gateway_multicast_group_source.html.markdown @@ -0,0 +1,36 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_transit_gateway_multicast_group_source" +description: |- + Manages an EC2 Transit Gateway Multicast Group Source +--- + +# Resource: aws_ec2_transit_gateway_multicast_group_source + +Registers sources (network interfaces) with the transit gateway multicast group. +A multicast source is a network interface attached to a supported instance that sends multicast traffic. + +## Example Usage + +```terraform +resource "aws_ec2_transit_gateway_multicast_group_source" "example" { + group_ip_address = "224.0.0.1" + network_interface_id = aws_network_interface.example.id + transit_gateway_multicast_domain_id = aws_ec2_transit_gateway_multicast_domain.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `group_ip_address` - (Required) The IP address assigned to the transit gateway multicast group. +* `network_interface_id` - (Required) The group members' network interface ID to register with the transit gateway multicast group. +* `transit_gateway_multicast_domain_id` - (Required) The ID of the transit gateway multicast domain. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - EC2 Transit Gateway Multicast Group Member identifier.