def _delete_region_vpc(cls, region): """delete VPC from AWS region with correct namespace tag""" region_vpc = aws.get_region_vpc(region) if region_vpc is not None: cls._delete_vpc_sgs(region, region_vpc['VpcId']) cls._delete_vpc_subnets(region, region_vpc['VpcId']) cls._delete_vpc_internet_gateways(region, region_vpc['VpcId']) aws.ec2_client(region).delete_vpc(VpcId=region_vpc['VpcId']) return True return False
def _probe_region(region, instances): """return elastic IP addresses in region Requires ec2:DescribeAddresses permission. """ ec2_client = aws.ec2_client(region) addresses = ec2_client.describe_addresses(Filters=[ {'Name': "domain", 'Values': ["vpc"]}, {'Name': "tag:Namespace", 'Values': [consts.NAMESPACE]} ])['Addresses'] region_addresses = [] for address in addresses: address_info = { 'allocation_id': address['AllocationId'], 'ip': address['PublicIp'] } if 'AssociationId' in address: address_info['association_id'] = address['AssociationId'] if 'InstanceId' in address: for instance in instances: if (instance['id'] == address['InstanceId'] and instance['region'] == region): address_info['instance_name'] = instance['name'] break region_addresses.append(address_info) return sorted(region_addresses, key=lambda k: k['ip'])
def main(self, cmd_args): """associate elastic IP address to an (other) instance Args: cmd_args (namedtuple): See add_documentation method. """ address = find_addresses.main(cmd_args.ip) ec2_client = aws.ec2_client(address['region']) all_instances = find_instances.probe_regions() try: instance = next(instance for instance in all_instances if instance['name'] == cmd_args.name) except StopIteration: halt.err(f"Instance named \"{cmd_args.name}\" not found.") if instance['region'] != address['region']: halt.err("Instance and address are in different regions.") if 'instance_name' in address: if instance['name'] == address['instance_name']: halt.err("Address already associated with specified instance.") if 'association_id' in address and cmd_args.force is False: halt.err(f"Elastic IP address {address['ip']} currently in use.", " Append the -f argument to force disassociation.") with aws.ClientErrorHalt(): if 'association_id' in address: ec2_client.disassociate_address( AssociationId=address['association_id']) ec2_client.associate_address(AllocationId=address['allocation_id'], InstanceId=instance['id']) print("") print("Address associated with instance.")
def _delete_vpc_subnets(region, vpc_id): """delete VPC's subnet(s) from AWS region""" ec2_client = aws.ec2_client(region) vpc_subnets = ec2_client.describe_subnets(Filters=[{ 'Name': "vpc-id", 'Values': [vpc_id] }])['Subnets'] for vpc_subnet in vpc_subnets: ec2_client.delete_subnet(SubnetId=vpc_subnet['SubnetId'])
def _region_namespace_key_fingerprint(self, region): """return key fingerprint if region has namespace EC2 key pair""" key_pairs = aws.ec2_client(region).describe_key_pairs( Filters=[{ 'Name': "key-name", 'Values': [self._key_pair_name] }])['KeyPairs'] if key_pairs: return key_pairs[0]['KeyFingerprint'] return None
def _create_sg(cls, region, sg_name, sg_desc, vpc_id): """create new VPC security group on AWS""" ec2_client = aws.ec2_client(region) sg_id = ec2_client.create_security_group(Description=sg_desc, GroupName=sg_name, VpcId=vpc_id)['GroupId'] aws.attach_tags(region, sg_id, sg_name) local_sg_ingress = cls._get_json_sg_ingress(sg_name) if local_sg_ingress: ec2_client.authorize_security_group_ingress( GroupId=sg_id, IpPermissions=local_sg_ingress)
def main(self, cmd_args): """stop instance(s) Args: cmd_args (namedtuple): See find_instances:add_argparse_args """ instances = find_instances.main(cmd_args) instances_to_stop = False instances_stopping = False for instance in instances: print("") print(f"Attempting to stop {instance['name']} " f"({instance['id']})...") instance_state, _ = find_instances.get_state_and_ip( instance['region'], instance['id']) if instance_state == "stopped": print(" Instance is already stopped.") continue if instance_state == "stopping": instances_stopping = True print(" Instance is already in the process of stopping.") continue instances_to_stop = True aws.ec2_client(instance['region']).stop_instances( InstanceIds=[instance['id']]) print(f" Instance is now stopping.") print("") if instances_to_stop is True: print("Instance(s) may take a few minutes to fully stop.") elif instances_stopping is True: print("Instance(s) already stopping.") else: print("No instances to stop.")
def _create_vpc(cls, region): """create VPC with subnet(s) in region and attach tags""" ec2_client = aws.ec2_client(region) vpc_id = ec2_client.create_vpc( CidrBlock="172.31.0.0/16", AmazonProvidedIpv6CidrBlock=False)['Vpc']['VpcId'] aws.attach_tags(region, vpc_id, consts.NAMESPACE) ec2_client.modify_vpc_attribute(EnableDnsSupport={'Value': True}, VpcId=vpc_id) ec2_client.modify_vpc_attribute(EnableDnsHostnames={'Value': True}, VpcId=vpc_id) route_table_id = cls._create_internet_gateway(region, vpc_id) cls._create_vpc_subnets(region, vpc_id, route_table_id)
def _delete_vpc_internet_gateways(region, vpc_id): """detach (and delete) internet gateway(s) attached (solely) to VPC""" ec2_client = aws.ec2_client(region) gateways = ec2_client.describe_internet_gateways( Filters=[{ 'Name': "attachment.vpc-id", 'Values': [vpc_id] }])['InternetGateways'] for gateway in gateways: ec2_client.detach_internet_gateway( InternetGatewayId=gateway['InternetGatewayId'], VpcId=vpc_id) # Only delete gateway if attached solely to namespace VPC if len(gateway['Attachments']) == 1: ec2_client.delete_internet_gateway( InternetGatewayId=gateway['InternetGatewayId'])
def _update_sg(cls, region, sg_name, vpc_id): """update VPC security group that already exists on AWS""" ec2_client = aws.ec2_client(region) aws_sgs = aws.get_vpc_security_groups(region, vpc_id) sg_id = next(sg['GroupId'] for sg in aws_sgs if sg['GroupName'] == sg_name) aws_sg_ingress = next(sg['IpPermissions'] for sg in aws_sgs if sg['GroupName'] == sg_name) ec2_client.revoke_security_group_ingress(GroupId=sg_id, IpPermissions=aws_sg_ingress) local_sg_ingress = cls._get_json_sg_ingress(sg_name) if local_sg_ingress: ec2_client.authorize_security_group_ingress( GroupId=sg_id, IpPermissions=local_sg_ingress)
def main(self, cmd_args): """disassociate elastic IP address from its instance Args: cmd_args (namedtuple): See add_documentation method. """ address = find_addresses.main(cmd_args.ip) ec2_client = aws.ec2_client(address['region']) if 'association_id' not in address: halt.err("Elastic IP address not associated with anything.") with aws.ClientErrorHalt(): ec2_client.disassociate_address( AssociationId=address['association_id']) print("") print("Elastic IP address disassociated.")
def get_state_and_ip(region, instance_ip): """return state and (elastic) IP of instance Requires ec2:DescribeInstances permission. """ response = aws.ec2_client(region).describe_instances( InstanceIds=[instance_ip])['Reservations'][0]['Instances'][0] instance_state = response['State']['Name'] instance_ip = None for network_interface in response['NetworkInterfaces']: if 'Association' in network_interface: # IP is elastic unless association's 'IpOwnerId' is "amazon" instance_ip = network_interface['Association']['PublicIp'] break else: # Script expects running instances to always have a public IP if instance_state == "running": instance_state = "???" return (instance_state, instance_ip)
def _probe_region(region, tag_filter): """probe single AWS region for non-terminated named instances Requires ec2:DescribeInstances permission. Args: region (str): AWS region to probe. tag_filter (list[dict]): Filter out instances that don't have tags matching the filter. If None/empty, filter not used. Returns: list[dict]: Instance(s) found in region. 'id' (str): ID of instance. 'name' (str): Tag value for instance tag key "Name". 'tags' (dict): Instance tag key-value pair(s). """ reservations = aws.ec2_client(region).describe_instances( Filters=tag_filter)['Reservations'] region_instances = [] for reservation in reservations: for instance in reservation['Instances']: if instance['State']['Name'] in ("shutting-down", "terminated"): continue if not any(tag['Key'] == "Name" for tag in instance['Tags']): continue region_instances.append({ 'id': instance['InstanceId'], 'tags': dict( sorted( {tag['Key']: tag['Value'] for tag in instance['Tags']}.items())) }) region_instances[-1].update( {'name': region_instances[-1]['tags'].pop('Name')}) return sorted(region_instances, key=lambda k: k['name'])
def _create_internet_gateway(region, vpc_id): """create internet gateway, attach it to VPC, and configure route""" ec2_client = aws.ec2_client(region) ig_id = ec2_client.create_internet_gateway( )['InternetGateway']['InternetGatewayId'] aws.attach_tags(region, ig_id, consts.NAMESPACE) ec2_client.attach_internet_gateway(InternetGatewayId=ig_id, VpcId=vpc_id) # VPC's automatically created main route table is used rt_id = ec2_client.describe_route_tables(Filters=[{ 'Name': "vpc-id", 'Values': [vpc_id] }, { 'Name': "association.main", 'Values': ["true"] }])['RouteTables'][0]['RouteTableId'] aws.attach_tags(region, rt_id, consts.NAMESPACE) ec2_client.create_route(DestinationCidrBlock="0.0.0.0/0", GatewayId=ig_id, RouteTableId=rt_id) return rt_id
def _create_vpc_subnets(region, vpc_id, rt_id): """create VPC subnet in AWS region for each availability zone Args: region (str): Region to create subnets in. vpc_id (str): ID of VPC to create subnets under. rt_id (str): ID of route table to attach subnets to. """ ec2_client = aws.ec2_client(region) azs = ec2_client.describe_availability_zones()['AvailabilityZones'] for index, az in enumerate(azs): if az['State'] != "available": continue if index * 16 >= 256: break subnet_id = ec2_client.create_subnet( AvailabilityZone=az['ZoneName'], CidrBlock=f"172.31.{index*16}.0/20", VpcId=vpc_id)['Subnet']['SubnetId'] ec2_client.modify_subnet_attribute( MapPublicIpOnLaunch={'Value': True}, SubnetId=subnet_id) ec2_client.associate_route_table(RouteTableId=rt_id, SubnetId=subnet_id)
def main(self, cmd_args): """release elastic IP address (give up possession) Args: cmd_args (namedtuple): See add_documentation method. """ address = find_addresses.main(cmd_args.ip) ec2_client = aws.ec2_client(address['region']) if 'association_id' in address and cmd_args.force is False: halt.err(f"Elastic IP address {address['ip']} currently in use.", " Append the -f argument to force disassociation.") print("") if 'association_id' in address: ec2_client.disassociate_address( AssociationId=address['association_id']) ec2_client.release_address(AllocationId=address['allocation_id']) if 'association_id' in address: print(f"Elastic IP address {address['ip']} " "disassociated and released.") else: print(f"Elastic IP address {address['ip']} released.")
def _create_region_key_pair(self, region, public_key_bytes): """create EC2 key pair in region and return public key fingerprint""" return aws.ec2_client(region).import_key_pair( KeyName=self._key_pair_name, PublicKeyMaterial=public_key_bytes)['KeyFingerprint']
def _delete_vpc_sgs(region, vpc_id): """delete VPC's security group(s) from AWS region""" ec2_client = aws.ec2_client(region) aws_sgs = aws.get_vpc_security_groups(region, vpc_id) for aws_sg in aws_sgs: ec2_client.delete_security_group(GroupId=aws_sg['GroupId'])
def _delete_region_key_pair(self, region): """delete namespace EC2 key pair from region""" if self._region_namespace_key_fingerprint(region) is not None: aws.ec2_client(region).delete_key_pair(KeyName=self._key_pair_name) return True return False
def __init__(self, cmd_args): self._ec2_client = aws.ec2_client(cmd_args.region)