def update_route_tables(target_peerings: list, metadata: Mapping, dryrun: bool) -> None: """ Loops through a list of peerings and updates the route tables on each side. Example target_peerings: ``` [ [ { "account_id": 415432961280, "vpc_id": "vpc-e08fb484", "region": "ap-southeast-2", "cidr_overrides": [ "10.53.101.0/27" ], "peering_tags": [ { "peerd_az_affinity": "0" } ] }, { "account_id": 415432961280, "vpc_id": "vpc-7a83b81e", "region": "ap-southeast-2" } ] ] ``` :param target_peerings: A list of lists representing the requester and accepter for each peering. :type target_peerings: list :param metadata: A dictionary with the environment, owner, etc for tagging :type metadata: list """ # We need to handle both sides of the peerings so we append reverse of each peering. # This means every side of every peering will be seen by a single loop. # We do this to avoid extra code handling the AWS concept of "accepter" and "requester" # We also construct our route table cache by account and region, which means if we looped # by requester and accepter we could cache stale route table contents. target_peerings.extend([x[::-1] for x in target_peerings]) # Loop through the target peerings for peering_descriptor in target_peerings: # Unpack some common variables account_id = peering_descriptor[0]['account_id'] vpc_id = peering_descriptor[0]['vpc_id'] region = peering_descriptor[0]['region'] remote_account_id = peering_descriptor[1]['account_id'] remote_vpc_id = peering_descriptor[1]['vpc_id'] remote_region = peering_descriptor[1]['region'] # Get the remote CIDRs from the AWS VPC API, or use the overrides in the config if they exist. remote_cidrs = peering_descriptor[1].get( 'cidr_overrides', list_vpc_cidrs(remote_vpc_id, remote_account_id, remote_region)) LOGGER.info( f"Inspecting route tables in {account_id} {vpc_id} " f"{region}, peer: {remote_account_id} {remote_vpc_id} {remote_region}" ) # Initialise a ec2 client connection ec2_client = aws_client(account_id, 'ec2', region) # Get active VPC peering if one exists, else continue # We want to avoid adding routes for inactive peerings. filters = [{ 'Name': 'tag:peerd_created', 'Values': ['true'] }, { 'Name': 'tag:peerd_environment', 'Values': [metadata['environment']] }, { 'Name': 'status-code', 'Values': ['active', 'provisioning'] }] if not (peering := get_vpc_peering(vpc_id, remote_vpc_id, account_id, region, filters)): # Since we filter for the peerd environment, remind the user that there could be a peering # But that it might exist as part of another environment, and thus we won't be touching it. LOGGER.warning( f'No active peering between {vpc_id} and {remote_vpc_id} for this environment' f' {metadata["environment"]}. It may exist as part of another environment.' ) continue peering_id = peering['VpcPeeringConnectionId'] # Wait until the peering is active, not provisioning (boto waiter doesn't accept filters for vpc peering api) # We must wait for active state to install routes, otherwise the peering will be ignored. # Note, usually this step takes a few seconds, but can sometimes take up to a minute or two in rare cases. if peering['Status']['Code'] == 'provisioning': while not ec2_client.describe_vpc_peering_connections( Filters=[{ 'Name': 'status-code', 'Values': ['active'] }, { 'Name': 'vpc-peering-connection-id', 'Values': [peering_id] }])['VpcPeeringConnections']: LOGGER.info( f'Waiting for peering {peering_id} to become active...') sleep(5) # Get the route tables for the local vpc relevant to the peering. # The vpc_route_tables function will only return peerd_eligible:true tables # Since we will have cases where RTs should not be altered. if not (route_tables := vpc_route_tables(vpc_id, account_id, region)): LOGGER.warning(f'No peerd_eligible route tables in VPC {vpc_id}') continue
def create_vpc_peerings(target_peerings: Sequence, metadata: Mapping, dryrun: bool) -> None: """ Loops through a list of peerings to create them. Requests, accepts and tags them. Repairs any half open peerings. Example target_peerings: ``` [ [ { "account_id": 415432961280, "vpc_id": "vpc-e08fb484", "region": "ap-southeast-2", "cidr_overrides": [ "10.53.101.0/27" ], "peering_tags": [ { "peerd_az_affinity": "0" } ] }, { "account_id": 415432961280, "vpc_id": "vpc-7a83b81e", "region": "ap-southeast-2" } ] ] ``` :param target_peerings: A list of lists representing the requester and accepter for each peering. :type target_peerings: list :param metadata: A dictionary with the environment, owner, etc for tagging :type metadata: list """ pending_acceptance_peerings = [] for peering_descriptor in target_peerings: # Unpack some common variables account_id = peering_descriptor[0]['account_id'] vpc_id = peering_descriptor[0]['vpc_id'] region = peering_descriptor[0]['region'] local_tags = peering_descriptor[0].get('peering_tags', {}) remote_account_id = peering_descriptor[1]['account_id'] remote_vpc_id = peering_descriptor[1]['vpc_id'] remote_region = peering_descriptor[1]['region'] remote_tags = peering_descriptor[1].get('peering_tags', {}) # Create a VPC peering request try: ec2_client = aws_client(account_id, 'ec2', region) # If the peering doesn't exist, create it if not (peering := get_vpc_peering(vpc_id, remote_vpc_id, account_id, region)): LOGGER.info( f"Creating peering request between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}" ) try: ec2_peering_response = ec2_client.create_vpc_peering_connection( VpcId=vpc_id, PeerVpcId=remote_vpc_id, PeerOwnerId=remote_account_id, PeerRegion=remote_region, DryRun=dryrun)['VpcPeeringConnection'] except ClientError as err: if err.response['Error']['Code'] == 'DryRunOperation': continue raise peering_id = ec2_peering_response['VpcPeeringConnectionId'] # Wait for the vpc peering to exist before moving on LOGGER.info(f'Waiting for peering {peering_id} to exist...') ec2_client.get_waiter('vpc_peering_connection_exists').wait( VpcPeeringConnectionIds=[peering_id], WaiterConfig={'Delay': 5}) # If the peering exists and is active, do nothing. elif peering['Status']['Code'] == 'active': LOGGER.info( f"Active peering {peering['VpcPeeringConnectionId']} between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}" ) continue # If the peering is pending acceptance move to tagging and acceptance # Only the remote account can accept the VPC peering. elif peering['Status']['Code'] == 'pending-acceptance' and peering[ 'RequesterVpcInfo']['VpcId'] == vpc_id: peering_id = peering['VpcPeeringConnectionId'] LOGGER.warning( f"Pending-Acceptance peering {peering_id} between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}. Will attempt recovery." )