import os import json import uuid from ipaddress import IPv4Network import boto3 import botocore import requests ec2_resource = boto3.resource('ec2') ec2_client = boto3.client('ec2') INTERFACE_NAMES = { 'enb': { 0: 'Mgmt-Iface', 1: 'S1U-Iface' }, 'mec': { 0: 'Mgmt-Iface', 1: 'S1U-Iface', 2: 'SGI-Iface' }, 'gilan': { 0: 'Mgmt-Iface', 1: 'SGI-Iface' }, 'cp': { 0: 'Mgmt-Iface' } } class EC2Instance: def __init__(self, event): self.vpc_type = event['ResourceProperties']['VPCType'] self.image_id = event['ResourceProperties']['ImageID'] self.instance_name = event['ResourceProperties']['InstanceName'] self.instance_count = int(event['ResourceProperties']['InstanceCount']) self.security_group = event['ResourceProperties']['SecurityGroup'] self.subnet_ids = event['ResourceProperties']['SubnetIds'] self.key_pair = event['ResourceProperties']['KeyPair'] self.instance_type = event['ResourceProperties']['InstanceType'] self.stack_id = event['StackId'] if event['ResourceProperties'].get('DestinationCIDR'): destination_cidr = event['ResourceProperties'].get('DestinationCIDR') new_prefix = 24 if self.vpc_type == 'enb' else 16 self.destination_cidr = IPv4Network(destination_cidr).subnets(new_prefix=new_prefix) def fetch_route_table_id(self, subnet_id=None): if not subnet_id: subnet_id = self.subnet_ids[-1] response = ec2_client.describe_route_tables( Filters=[ { 'Name': 'association.subnet-id', 'Values': [subnet_id] }]) if response.get('RouteTables'): route_table_id = response['RouteTables'][0]['Associations'][0]['RouteTableId'] print(f'Route Table id : {route_table_id}') else: route_table_id = '' return route_table_id def add_route(self, interface_id): route_table_id = self.fetch_route_table_id() if route_table_id: route_table = ec2_resource.RouteTable(route_table_id) route_table.create_route( DestinationCidrBlock=str(next(self.destination_cidr)), NetworkInterfaceId=interface_id ) print(f'Added route with {interface_id}') else: print("Error adding route") def fetch_instances(self): reservations = ec2_client.describe_instances( Filters=[ { "Name": "tag:StackId", "Values": [ self.stack_id ] }, { 'Name': 'instance-state-name', 'Values': [ 'pending', 'running' ] } ] ) instances = list() for reservation in reservations['Reservations']: instances.extend(reservation['Instances']) # instances = response['Reservations'][0]['Instances'] print(f'Instances : {instances}') return instances def create_instances(self): for i in range(self.instance_count): instance_name = f'{self.instance_name}{i+1}' interfaces = self.create_interfaces(instance_name) self.create_instance(interfaces, instance_name) instances = self.fetch_instances() if len(instances) == self.instance_count: return 'SUCCESS' def create_interface(self, subnet_id): network_interface_id = None if subnet_id: try: network_interface = ec2_resource.create_network_interface(Groups=[self.security_group], SubnetId=subnet_id) network_interface_id = network_interface.id print("Created network interface: {}".format(network_interface_id)) print(network_interface.attachment) except botocore.exceptions.ClientError as e: print("Error creating network interface: {}".format(e.response['Error'])) return network_interface_id def create_interfaces(self, instance_name): interfaces = [] for id, subnet_id in enumerate(self.subnet_ids): interface_id = self.create_interface(subnet_id) network_interface = ec2_resource.NetworkInterface(interface_id) if id != 0: network_interface.modify_attribute( SourceDestCheck={ 'Value': False } ) network_interface.create_tags( Tags=[ { 'Key': 'Name', 'Value': f'{instance_name}-{INTERFACE_NAMES[self.vpc_type][id]}' } ] ) interface = {'NetworkInterfaceId': interface_id, 'DeviceIndex': id} interfaces.append(interface) return interfaces def create_instance(self, network_interfaces, instance_name): try: instances = ec2_resource.create_instances( ImageId=self.image_id, InstanceType=self.instance_type, KeyName=self.key_pair, MinCount=1, MaxCount=1, NetworkInterfaces=network_interfaces ) instance = instances[0] print(f'Created instance {instance.id}') instance.create_tags( Tags=[ { 'Key': 'Name', 'Value': instance_name }, { 'Key': 'StackId', 'Value': self.stack_id } ] ) self.set_delete_on_termination(instance) except botocore.exceptions.ClientError as e: print("Error creating instance {}".format(e.response['Error'])) if self.vpc_type in ('enb', 'mec'): self.add_route(network_interfaces[-1]['NetworkInterfaceId']) def set_delete_on_termination(self, instance): network_interfaces = instance.network_interfaces for interface in network_interfaces: attachment = interface.attachment print(attachment) attachment_id = attachment['AttachmentId'] try: interface.modify_attribute( Attachment={ 'AttachmentId': attachment_id, 'DeleteOnTermination': True } ) print(f"Updated DeleteOnTermination for {interface.id}") except botocore.exceptions.ClientError as e: print("Error in fetching instance details: {}".format(e.response['Error'])) def delete_routes(self): route_table_id = self.fetch_route_table_id() for i in range(self.instance_count): destination_cidr = str(next(self.destination_cidr)) route = ec2_resource.Route(route_table_id, destination_cidr) route.delete() def delete_instances(self): try: if self.vpc_type in ('enb', 'mec'): self.delete_routes() instances = self.fetch_instances() instance_ids = [instance["InstanceId"] for instance in instances] print(instance_ids) if instance_ids: ec2_client.terminate_instances( InstanceIds=instance_ids ) except botocore.exceptions.ClientError as e: print("Error in deleting instances: {}".format(e.response['Error'])) instances = self.fetch_instances() if len(instances) == 0: return "SUCCESS" def get_supernet_ip_cidr(event): ip_cidr = event['ResourceProperties']['VPCCIDR'] try: ip_net = IPv4Network(ip_cidr) supernet_ip_cidr = ip_net.supernet(new_prefix=8) except ValueError: supernet_ip_cidr = '' return str(supernet_ip_cidr) def send_response(event, result, data={}, reason=None, physical_resource_id=None): if result not in ['SUCCESS', 'FAILED']: raise Exception("Result is not a valid result") response = {'Status': result, 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': data} if reason: response['Reason'] = reason if physical_resource_id: response['PhysicalResourceId'] = physical_resource_id else: response['PhysicalResourceId'] = uuid.uuid4().hex json_object = json.dumps(response) headers = { 'content-type': '', 'content-length': str(len(json_object)) } print(json_object) requests.put(event['ResponseURL'], data=json_object, headers=headers) def lambda_handler(event, context): print(json.dumps(event)) print('## ENVIRONMENT VARIABLES') print(os.environ) print('## EVENT') print(event) data = dict() # physical_resource_id = context['logStreamName'] try: if event['RequestType'] == 'Create': ec2_instance = EC2Instance(event) result = ec2_instance.create_instances() if result == 'SUCCESS': send_response(event, 'SUCCESS', data={}) else: send_response(event, 'FAILED') elif event['RequestType'] == 'Delete': ec2_instance = EC2Instance(event) result = ec2_instance.delete_instances() if result == 'SUCCESS': send_response(event, 'SUCCESS', data=data) else: send_response(event, 'FAILED') elif event['RequestType'] == 'Update': ec2_instance = EC2Instance(event) result_delete = ec2_instance.delete_instances() result_create = ec2_instance.create_instances() if result_delete == 'SUCCESS' and result_create == 'SUCCESS': send_response(event, 'SUCCESS', data={}) else: send_response(event, 'FAILED') except Exception as e: # Failure occurred # Sending an exception based response response = { 'Status': 'FAILED', 'Reason': str(e), 'PhysicalResourceId': event.get('PhysicalResourceId', 'DoesNotMatter'), 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': {} } json_object = json.dumps(response) headers = { 'content-type': '', 'content-length': str(len(json_object)) } print(json_object) requests.put(event['ResponseURL'], data=json_object, headers=headers) def handler_supernet_ip_cidr(event, context): print(json.dumps(event)) print('## ENVIRONMENT VARIABLES') print(os.environ) print('## EVENT') print(event) data = {} # physical_resource_id = context['logStreamName'] try: if event['RequestType'] == 'Create': supernet_ip_cidr = get_supernet_ip_cidr(event) if supernet_ip_cidr: data['SupernetCIDR'] = supernet_ip_cidr send_response(event, 'SUCCESS', data=data) else: send_response(event, 'FAILED') elif event['RequestType'] == 'Delete': send_response(event, 'SUCCESS') elif event['RequestType'] == 'Update': supernet_ip_cidr = get_supernet_ip_cidr(event) if supernet_ip_cidr: data['SupernetCIDR'] = supernet_ip_cidr send_response(event, 'SUCCESS', data=data) else: send_response(event, 'FAILED') except Exception as e: # Failure occurred # Sending an exception based response response = { 'Status': 'FAILED', 'Reason': str(e), 'PhysicalResourceId': event.get('PhysicalResourceId', 'DoesNotMatter'), 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': {} } json_object = json.dumps(response) headers = { 'content-type': '', 'content-length': str(len(json_object)) } print(json_object) requests.put(event['ResponseURL'], data=json_object, headers=headers)