class VPCEndpointState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC endpoint.""" definition_type = VPCEndpointDefinition state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + [ "endpointId", "creationToken", ] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_endpoint = Handler( ["region", "serviceName", "vpcId"], handle=self.realize_create_endpoint) self.handle_modify_endpoint = Handler( ["policy", "routeTableIds"], after=[self.handle_create_endpoint], handle=self.realize_modify_endpoint, ) @classmethod def get_type(cls): return "vpc-endpoint" def show_type(self): s = super(VPCEndpointState, self).show_type() if self._state.get("region", None): s = "{0} [{1}]".format(s, self._state["region"]) return s @property def resource_id(self): return self._state.get("endpointId", None) def prefix_definition(self, attr): return {("resources", "vpcEndpoints"): attr} def get_defintion_prefix(self): return "resources.vpcEndpoints" def create_after(self, resources, defn): return { r for r in resources if isinstance(r, VPCState) or isinstance(r, VPCRouteTableState) } def realize_create_endpoint(self, allow_recreate): config: VPCEndpointDefinition = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "vpc endpoint {} definition changed" " use --allow-recreate if you want to create a new one". format(self._state["endpointId"])) self.warn("vpc endpoint changed, recreating...") self._destroy() self._state["region"] = config.config.region vpc_id = config.config.vpcId if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc", VPCState) vpc_id = res._state["vpcId"] if not self._state.get("creationToken", None): self._state["creationToken"] = str(uuid.uuid4()) self.state = self.STARTING response = self.get_client().create_vpc_endpoint( ClientToken=self._state["creationToken"], ServiceName=config.config.serviceName, VpcId=vpc_id, ) endpoint_id = response["VpcEndpoint"]["VpcEndpointId"] with self.depl._db: self.state = self.UP self._state["endpointId"] = endpoint_id self._state["vpcId"] = vpc_id self._state["serviceName"] = config.config.serviceName def realize_modify_endpoint(self, allow_recreate): config: VPCEndpointDefinition = self.get_defn() old_rtbs = self._state.get("routeTableIds", []) new_rtbs = [] for rtb in config.config.routeTableIds: if rtb.startswith("res-"): res = self.depl.get_typed_resource(rtb[4:].split(".")[0], "vpc-route-table", VPCRouteTableState) new_rtbs.append(res._state["routeTableId"]) else: new_rtbs.append(rtb) to_remove = [r for r in old_rtbs if r not in new_rtbs] to_add = [r for r in new_rtbs if r not in old_rtbs] edp_input: Dict[str, Any] = dict() edp_input["AddRouteTableIds"] = to_add edp_input["RemoveRouteTableIds"] = to_remove if config.config.policy is not None: edp_input["PolicyDocument"] = config.config.policy edp_input["VpcEndpointId"] = self._state["endpointId"] self.get_client().modify_vpc_endpoint(**edp_input) with self.depl._db: self._state["policy"] = config.config.policy self._state["routeTableIds"] = new_rtbs def _destroy(self): if self.state != self.UP: return try: self.log("deleting vpc endpoint {}".format( self._state["endpointId"])) self.get_client().delete_vpc_endpoints( VpcEndpointIds=[self._state["endpointId"]]) except botocore.exceptions.ClientError as e: if e.response["Error"]["Code"] == "InvalidVpcEndpointId.NotFound": self.warn("vpc endpoint {} was already deleted".format( self._state["endpointId"])) else: raise e with self.depl._db: self.state = self.MISSING self._state["endpointId"] = None self._state["vpcId"] = None self._state["serviceName"] = None self._state["policy"] = None self._state["routeTableIds"] = None def destroy(self, wipe=False): self._destroy() return True
class VPCCustomerGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC customer gateway.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["customerGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_customer_gtw = Handler(['region', 'publicIp', 'bgpAsn', 'type'], handle=self.realize_create_customer_gtw) self.handle_tag_update = Handler(['tags'], after=[self.handle_create_customer_gtw], handle=self.realize_update_tag) @classmethod def get_type(cls): return "vpc-customer-gateway" def show_type(self): s = super(VPCCustomerGatewayState, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self._state.get('customerGatewayId', None) def prefix_definition(self, attr): return {('resources', 'vpcCustomerGateways'): attr} def get_defintion_prefix(self): return "resources.vpcCustomerGateways." def realize_create_customer_gtw(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("customer gateway {} defintion changed" " use --allow-recreate if you want to create a new one".format( self._state['customerGatewayId'])) self.warn("customer gateway changed, recreating...") self._destroy() self._state['region'] = config['region'] self.log("creating customer gateway") response = self.get_client().create_customer_gateway( BgpAsn=config['bgpAsn'], PublicIp=config['publicIp'], Type=config['type']) customer_gtw_id = response['CustomerGateway']['CustomerGatewayId'] with self.depl._db: self.state = self.STARTING waiter = self.get_client().get_waiter('customer_gateway_available') waiter.wait(CustomerGatewayIds=[customer_gtw_id]) with self.depl._db: self.state = self.UP self._state['region'] = config['region'] self._state['customerGatewayId'] = customer_gtw_id self._state['bgpAsn'] = config['bgpAsn'] self._state['publicIp'] = config['publicIp'] self._state['type'] = config['type'] def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['customerGatewayId']], Tags=[{"Key": k, "Value": tags[k]} for k in tags]) def _destroy(self): if self.state != self.UP: return self.log("deleting customer gateway {}".format(self._state['customerGatewayId'])) try: self.get_client().delete_customer_gateway(CustomerGatewayId=self._state['customerGatewayId']) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidCustomerGatewayID.NotFound": self.warn("customer gateway {} was already deleted".format(self._state['customerGatewayId'])) else: raise e #TODO wait for customer gtw to be deleted with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['customerGatewayId'] = None self._state['bgpAsn'] = None self._state['publicIp'] = None self._state['type'] = None def destroy(self, wipe=False): self._destroy() return True
class AWSVPNGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a AWS VPN gateway.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["vpnGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_vpn_gtw = Handler(['region', 'zone', 'vpcId'], handle=self.realize_create_vpn_gtw) self.handle_tag_update = Handler(['tags'], after=[self.handle_create_vpn_gtw], handle=self.realize_update_tag) @classmethod def get_type(cls): return "aws-vpn-gateway" def show_type(self): s = super(AWSVPNGatewayState, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self._state.get('vpnGatewayId', None) def prefix_definition(self, attr): return {('resources', 'awsVPNGateways'): attr} def get_defintion_prefix(self): return "resources.awsVPNGateways." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc.VPCState)} def realize_create_vpn_gtw(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("VPN gateway {} defintion changed" " use --allow-recreate if you want to create a new one".format( self._state['vpnGatewayId'])) self.warn("VPN gateway changed, recreating...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating VPN gateway in zone {}".format(config['zone'])) response = self.get_client().create_vpn_gateway( AvailabilityZone=config['zone'], Type="ipsec.1") vpn_gtw_id = response['VpnGateway']['VpnGatewayId'] self.log("attaching vpn gateway {0} to vpc {1}".format(vpn_gtw_id, vpc_id)) self.get_client().attach_vpn_gateway( VpcId=vpc_id, VpnGatewayId=vpn_gtw_id) #TODO wait for the attchement state with self.depl._db: self.state = self.UP self._state['vpnGatewayId'] = vpn_gtw_id self._state['vpcId'] = vpc_id self._state['zone'] = config['zone'] def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['vpnGatewayId']], Tags=[{"Key": k, "Value": tags[k]} for k in tags]) def _destroy(self): if self.state != self.UP: return self.log("detaching vpn gateway {0} from vpc {1}".format(self._state['vpnGatewayId'], self._state['vpcId'])) try: self.get_client().detach_vpn_gateway( VpcId=self._state['vpcId'], VpnGatewayId=self._state['vpnGatewayId']) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidVpnGatewayAttachment.NotFound": self.warn("VPN gateway '{0}' attachment with VPC '{1}' is invalid".format( self._state['vpnGatewayId'], self._state['vpcId'])) else: raise e # TODO delete VPN connections associated with this VPN gtw self.log("deleting vpn gateway {}".format(self._state['vpnGatewayId'])) try: self.get_client().delete_vpn_gateway( VpnGatewayId=self._state['vpnGatewayId']) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidVpnGatewayID.NotFound": self.warn("VPN gateway {} was already deleted".format(self._state['vpnGatewayId'])) else: raise e with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['vpnGatewayId'] = None self._state['vpcId'] = None self._state['zone'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCEgressOnlyInternetGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC egress only internet gateway.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["egressOnlyInternetGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_igw = Handler(['region', 'vpcId'], handle=self.realize_create_gtw) @classmethod def get_type(cls): return "vpc-egress-only-internet-gateway" def show_type(self): s = super(VPCEgressOnlyInternetGatewayState, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self._state.get('egressOnlyInternetGatewayId', None) def prefix_definition(self, attr): return {('resources', 'vpcEgressOnlyInternetGateways'): attr} def get_defintion_prefix(self): return "resources.vpcEgressOnlyInternetGateways." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixopsaws.resources.vpc.VPCState) or isinstance(r, nixopsaws.resources.elastic_ip.ElasticIPState)} def realize_create_gtw(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("egress only internet gateway {} defintion changed" " use --allow-recreate if you want to create a new one".format( self._state['egressOnlyInternetGatewayId'])) self.warn("egress only internet gateway changed, recreating...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating egress only internet gateway in region {0}, vpc {1}".format(self._state['region'], vpc_id)) response = self.get_client().create_egress_only_internet_gateway(VpcId=vpc_id) igw_id = response['EgressOnlyInternetGateway']['EgressOnlyInternetGatewayId'] with self.depl._db: self.state = self.UP self._state['region'] = config['region'] self._state['vpcId'] = vpc_id self._state['egressOnlyInternetGatewayId'] = igw_id def _destroy(self): if self.state != self.UP: return self.log("deleting egress only internet gateway {0}".format(self._state['egressOnlyInternetGatewayId'])) self.get_client().delete_egress_only_internet_gateway(EgressOnlyInternetGatewayId=self._state['egressOnlyInternetGatewayId']) with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['vpcId'] = None self._state['egressOnlyInternetGatewayId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCDhcpOptionsState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC DHCP options.""" definition_type = VPCDhcpOptionsDefinition state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["dhcpOptionsId"] @classmethod def get_type(cls): return "vpc-dhcp-options" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) handled_keys = [ "region", "vpcId", "domainNameServers", "domainName", "ntpServers", "netbiosNameServers", "netbiosNodeType", ] self.handle_create_dhcp_options = Handler( handled_keys, handle=self.realize_create_dhcp_options) self.handle_tag_update = Handler( ["tags"], after=[self.handle_create_dhcp_options], handle=self.realize_update_tag, ) def show_type(self): s = super(VPCDhcpOptionsState, self).show_type() region = self._state.get("region", None) if region: s = "{0} [{1}]".format(s, region) return s @property def resource_id(self): return self._state.get("dhcpOptionsId", None) def prefix_definition(self, attr): return {("resources", "vpcDhcpOptions"): attr} def get_physical_spec(self): return {} def get_definition_prefix(self): return "resources.vpcDhcpOptions." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, vpc.VPCState)} def get_dhcp_config_option(self, key, values): val = values if isinstance(values, list) else [str(values)] return {"Key": key, "Values": val} def generate_dhcp_configuration(self, config): configuration = [] def check_and_append(option, key): if config[option]: configuration.append( self.get_dhcp_config_option(key=key, values=config[option])) check_and_append("domainNameServers", "domain-name-servers") check_and_append("domainName", "domain-name") check_and_append("ntpServers", "ntp-servers") check_and_append("netbiosNameServers", "netbios-name-servers") check_and_append("netbiosNodeType", "netbios-node-type") return configuration def realize_create_dhcp_options(self, allow_recreate): config: VPCDhcpOptionsDefinition = self.get_defn() if self.state == (self.UP or self.STARTING): if not allow_recreate: raise Exception( "to recreate the dhcp options please add --allow-recreate" " to the deploy command") self.warn("the dhcp options {} will be destroyed and re-created") self._destroy() self._state["region"] = config.config.region vpc_id = config.config.vpcId if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc", VPCState) vpc_id = res._state["vpcId"] dhcp_config = self.generate_dhcp_configuration(config) def create_dhcp_options(dhcp_config): self.log("creating dhcp options...") response = self.get_client().create_dhcp_options( DhcpConfigurations=dhcp_config) return response.get("DhcpOptions").get("DhcpOptionsId") dhcp_options_id = create_dhcp_options(dhcp_config) with self.depl._db: self.state = self.STARTING self._state["vpcId"] = vpc_id self._state["dhcpOptionsId"] = dhcp_options_id self._state["domainName"] = config.config.domainName self._state["domainNameServers"] = config.config.domainNameServers self._state["ntpServers"] = config.config.ntpServers self._state[ "netbiosNameServers"] = config.config.netbiosNameServers self._state["netbiosNodeType"] = config.config.netbiosNodeType self.get_client().associate_dhcp_options(DhcpOptionsId=dhcp_options_id, VpcId=vpc_id) with self.depl._db: self.state = self.UP def realize_update_tag(self, allow_recreate): config: VPCDhcpOptionsDefinition = self.get_defn() tags = {k: v for k, v in config.config.tags.items()} tags.update(self.get_common_tags()) self.get_client().create_tags( Resources=[self._state["dhcpOptionsId"]], Tags=[{ "Key": k, "Value": tags[k] } for k in tags], ) def _destroy(self): if self.state != self.UP: return dhcp_options_id = self._state.get("dhcpOptionsId", None) if dhcp_options_id: self.log("deleting dhcp options {0}".format(dhcp_options_id)) try: self.get_client().associate_dhcp_options( DhcpOptionsId="default", VpcId=self._state["vpcId"]) self.get_client().delete_dhcp_options( DhcpOptionsId=dhcp_options_id) except botocore.exceptions.ClientError as e: if e.response["Error"][ "Code"] == "InvalidDhcpOptionsID.NotFound": self.warn("dhcp options {0} was already deleted".format( dhcp_options_id)) else: raise e with self.depl._db: self.state = self.MISSING self._state["vpcId"] = None self._state["dhcpOptions"] = None self._state["domainName"] = None self._state["domainNameServers"] = None self._state["ntpServers"] = None self._state["netbiosNameServers"] = None self._state["netbiosNodeType"] = None def destroy(self, wipe=False): self._destroy() return True
class VPCRouteState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC route""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED TARGETS = ['egressOnlyInternetGatewayId', 'gatewayId', 'instanceId', 'natGatewayId', 'networkInterfaceId'] @classmethod def get_type(cls): return "vpc-route" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) keys = ['region', 'routeTableId', 'destinationCidrBlock', 'destinationIpv6CidrBlock', 'egressOnlyInternetGatewayId', 'gatewayId', 'instanceId', 'natGatewayId', 'networkInterfaceId'] self.handle_create_route = Handler(keys, handle=self.realize_create_route) def show_type(self): s = super(VPCRouteState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return (self._state.get('destinationCidrBlock', None) or self._state.get('destinationIpv6CidrBlock', None)) def prefix_definition(self, attr): return {('resources', 'vpcRoutes'): attr} def get_definition_prefix(self): return "resources.vpcRoutes." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixopsaws.resources.vpc_route_table.VPCRouteTableState) or isinstance(r, nixopsaws.resources.vpc_internet_gateway.VPCInternetGatewayState) or isinstance(r, nixopsaws.resources.vpc_nat_gateway.VPCNatGatewayState)} def realize_create_route(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("route {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self.name)) self.warn("route definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] rtb_id = config['routeTableId'] if rtb_id.startswith("res-"): res = self.depl.get_typed_resource(rtb_id[4:].split(".")[0], "vpc-route-table") rtb_id = res._state['routeTableId'] route = dict() config = self.get_defn() num_targets = 0 for item in self.TARGETS: if config[item]: num_targets+=1 target = item if num_targets > 1: raise Exception("you should specify only 1 target from {}".format(str(self.TARGETS))) if (config['destinationCidrBlock'] is not None) and (config['destinationIpv6CidrBlock'] is not None): raise Exception("you can't set both destinationCidrBlock and destinationIpv6CidrBlock in one route") if config['destinationCidrBlock'] is not None: destination = 'destinationCidrBlock' if config['destinationIpv6CidrBlock'] is not None: destination = 'destinationIpv6CidrBlock' def retrieve_defn(option): cfg = config[option] if cfg.startswith("res-"): name = cfg[4:].split(".")[0] res_type = cfg.split(".")[1] attr = cfg.split(".")[2] if len(cfg.split(".")) > 2 else option res = self.depl.get_typed_resource(name, res_type) return res._state[attr] else: return cfg route['RouteTableId'] = rtb_id route[self.upper(target)] = retrieve_defn(target) route[self.upper(destination)] = config[destination] self.log("creating route {0} => {1} in route table {2}".format(retrieve_defn(target), config[destination], rtb_id)) self.get_client().create_route(**route) with self.depl._db: self.state = self.UP self._state[target] = route[self.upper(target)] self._state[destination] = config[destination] self._state['routeTableId'] = rtb_id def upper(self, option): return "%s%s" % (option[0].upper(), option[1:]) def _destroy(self): if self.state != self.UP: return destination = 'destinationCidrBlock' if ('destinationCidrBlock' in self._state.keys()) else 'destinationIpv6CidrBlock' self.log("deleting route to {0} from route table {1}".format(self._state[destination], self._state['routeTableId'])) try: args = dict() args[self.upper(destination)] = self._state[destination] args['RouteTableId'] = self._state['routeTableId'] self.get_client().delete_route(**args) except botocore.exceptions.ClientError as error: if error.response['Error']['Code'] == "InvalidRoute.NotFound": self.warn("route was already deleted") else: raise error with self.depl._db: self.state = self.MISSING self._state['routeTableId'] = None self._state[destination] = None for target in self.TARGETS: self._state[target] = None def destroy(self, wipe=False): self._destroy() return True
class AWSVPNConnectionRouteState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPN connection route""" state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED @classmethod def get_type(cls): return "aws-vpn-connection-route" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get("region", None) self.handle_create_vpn_route = Handler( ["region", "vpnConnectionId", "destinationCidrBlock"], handle=self.realize_create_vpn_route, ) def show_type(self): s = super(AWSVPNConnectionRouteState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get("destinationCidrBlock", None) def prefix_definition(self, attr): return {("resources", "awsVPNConnectionRoutes"): attr} def get_definition_prefix(self): return "resources.awsVPNConnectionRoutes." def create_after(self, resources, defn): return { r for r in resources if isinstance(r, aws_vpn_connection.AWSVPNConnectionState) } def realize_create_vpn_route(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "vpn connection route {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one". format(self.name)) self.warn("route definition changed, recreating ...") self._destroy() self._state["region"] = config["region"] vpn_conn_id = config["vpnConnectionId"] if vpn_conn_id.startswith("res-"): res = self.depl.get_typed_resource( vpn_conn_id[4:].split(".")[0], "aws-vpn-connection", AWSVPNConnectionState, ) vpn_conn_id = res._state["vpnConnectionId"] self.log("creating route to {0} using vpn connection {1}".format( config["destinationCidrBlock"], vpn_conn_id)) self.get_client().create_vpn_connection_route( DestinationCidrBlock=config["destinationCidrBlock"], VpnConnectionId=vpn_conn_id, ) with self.depl._db: self.state = self.UP self._state["vpnConnectionId"] = vpn_conn_id self._state["destinationCidrBlock"] = config[ "destinationCidrBlock"] def _destroy(self): if self.state != self.UP: return self.log("deleting route to {}".format( self._state["destinationCidrBlock"])) try: self.get_client().delete_vpn_connection_route( DestinationCidrBlock=self._state["destinationCidrBlock"], VpnConnectionId=self._state["vpnConnectionId"], ) except botocore.exceptions.ClientError as e: if (e.response["Error"]["Code"] == "InvalidRoute.NotFound" or e.response["Error"]["Code"] == "InvalidVpnConnectionID.NotFound"): self.warn("route or vpn connection was already deleted") else: raise e with self.depl._db: self.state = self.MISSING self._state["vpnConnectionId"] = None self._state["destinationCidrBlock"] = None def destroy(self, wipe=True): self._destroy() return True
class VPCNatGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC NAT gateway""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['natGatewayId', 'creationToken'] @classmethod def get_type(cls): return "vpc-nat-gateway" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_gtw = Handler(['region', 'subnetId', 'allocationId'], handle=self.realize_create_gtw) self.nat_gtw_id = self._state.get('natGatewayId', None) def show_type(self): s = super(VPCNatGatewayState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('natGatewayId', None) def prefix_definition(self, attr): return {('resources', 'vpcNatGateways'): attr} def get_physical_spec(self): return { 'natGatewayId': self._state.get('natGatewayId', None) } def get_definition_prefix(self): return "resources.vpcNatGateways." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc_subnet.VPCSubnetState) or isinstance(r, nixops.resources.elastic_ip.ElasticIPState)} def realize_create_gtw(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("nat gateway {} defintion changed" " use --allow-recreate if you want to create a new one".format( self._state['natGatewayId'])) self.warn("nat gateway changed, recreating...") self._destroy() self._state['region'] = config['region'] subnet_id = config['subnetId'] allocation_id = config['allocationId'] if allocation_id.startswith("res-"): res = self.depl.get_typed_resource(allocation_id[4:].split(".")[0], "elastic-ip") allocation_id = res.allocation_id if subnet_id.startswith("res-"): res = self.depl.get_typed_resource(subnet_id[4:].split(".")[0], "vpc-subnet") subnet_id = res._state['subnetId'] if not self._state.get('creationToken', None): self._state['creationToken'] = str(uuid.uuid4()) self.state = self.STARTING response = self.get_client().create_nat_gateway(ClientToken=self._state['creationToken'], AllocationId=allocation_id, SubnetId=subnet_id) gtw_id = response['NatGateway']['NatGatewayId'] with self.depl._db: self.state = self.UP self._state['subnetId'] = subnet_id self._state['allocationId'] = allocation_id self._state['natGatewayId'] = gtw_id def wait_for_nat_gtw_deletion(self): self.log("waiting for nat gateway {0} to be deleted".format(self._state['natGatewayId'])) while True: try: response = self.get_client().describe_nat_gateways( NatGatewayIds=[self._state['natGatewayId']] ) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidNatGatewayID.NotFound" or e.response['Error']['Code'] == "NatGatewayNotFound": self.warn("nat gateway {} was already deleted".format(self._state['natGatewayId'])) break else: raise if len(response['NatGateways'])==1: if response['NatGateways'][0]['State'] == "deleted": break elif response['NatGateways'][0]['State'] != "deleting": raise Exception("nat gateway {0} in an unexpected state {1}".format( self._state['natGatewayId'], response['NatGateways'][0]['State'])) self.log_continue(".") time.sleep(1) else: break self.log_end(" done") def _destroy(self): if self.state == self.UP: self.log("deleting vpc NAT gateway {}".format(self._state['natGatewayId'])) try: self.get_client().delete_nat_gateway(NatGatewayId=self._state['natGatewayId']) with self.depl._db: self.state = self.STOPPING except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidNatGatewayID.NotFound" or e.response['Error']['Code'] == "NatGatewayNotFound": self.warn("nat gateway {} was already deleted".format(self._state['natGatewayId'])) else: raise e if self.state == self.STOPPING: self.wait_for_nat_gtw_deletion() with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['subnetId'] = None self._state['allocationId'] = None self._state['natGatewayId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCInternetGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC internet gateway.""" definition_type = VPCInternetGatewayDefinition state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int ) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["internetGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_igw = Handler( ["region", "vpcId"], handle=self.realize_create_gtw ) self.handle_tag_update = Handler( ["tags"], after=[self.handle_create_igw], handle=self.realize_update_tag ) @classmethod def get_type(cls): return "vpc-internet-gateway" def show_type(self): s = super(VPCInternetGatewayState, self).show_type() if self._state.get("region", None): s = "{0} [{1}]".format(s, self._state["region"]) return s @property def resource_id(self): return self._state.get("internetGatewayId", None) def prefix_definition(self, attr): return {("resources", "vpcInternetGateways"): attr} def get_defintion_prefix(self): return "resources.vpcInternetGateways." def create_after(self, resources, defn): return { r for r in resources if isinstance(r, vpc.VPCState) or isinstance(r, elastic_ip.ElasticIPState) } def realize_create_gtw(self, allow_recreate): config: VPCInternetGatewayDefinition = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "internet gateway {} defintion changed" " use --allow-recreate if you want to create a new one".format( self._state["internetGatewayId"] ) ) self.warn("internet gateway changed, recreating...") self._destroy() self._state["region"] = config.config.region vpc_id = config.config.vpcId if vpc_id.startswith("res-"): res = self.depl.get_typed_resource( vpc_id[4:].split(".")[0], "vpc", VPCState ) vpc_id = res._state["vpcId"] self.log("creating internet gateway in region {}".format(self._state["region"])) response = self.get_client().create_internet_gateway() igw_id = response["InternetGateway"]["InternetGatewayId"] self.log("attaching internet gateway {0} to vpc {1}".format(igw_id, vpc_id)) self.get_client().attach_internet_gateway( InternetGatewayId=igw_id, VpcId=vpc_id ) with self.depl._db: self.state = self.UP self._state["region"] = config.config.region self._state["vpcId"] = vpc_id self._state["internetGatewayId"] = igw_id def realize_update_tag(self, allow_recreate): config: VPCInternetGatewayDefinition = self.get_defn() tags = {k: v for k, v in config.config.tags.items()} tags.update(self.get_common_tags()) self.get_client().create_tags( Resources=[self._state["internetGatewayId"]], Tags=[{"Key": k, "Value": tags[k]} for k in tags], ) def _destroy(self): if self.state != self.UP: return self.log( "detaching internet gateway {0} from vpc {1}".format( self._state["internetGatewayId"], self._state["vpcId"] ) ) self._retry( lambda: self.get_client().detach_internet_gateway( InternetGatewayId=self._state["internetGatewayId"], VpcId=self._state["vpcId"], ) ) self.log( "deleting internet gateway {0}".format(self._state["internetGatewayId"]) ) self.get_client().delete_internet_gateway( InternetGatewayId=self._state["internetGatewayId"] ) with self.depl._db: self.state = self.MISSING self._state["region"] = None self._state["vpcId"] = None self._state["internetGatewayId"] = None def destroy(self, wipe=False): self._destroy() return True
class VPCEndpointState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC endpoint.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["endpointId","creationToken"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_endpoint = Handler(['region', 'serviceName', 'vpcId'], handle=self.realize_create_endpoint) self.handle_modify_endpoint = Handler(['policy', 'routeTableIds'], after=[self.handle_create_endpoint], handle=self.realize_modify_endpoint) @classmethod def get_type(cls): return "vpc-endpoint" def show_type(self): s = super(VPCEndpointState, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self._state.get('endpointId', None) def prefix_definition(self, attr): return {('resources', 'vpcEndpoints'): attr} def get_defintion_prefix(self): return "resources.vpcEndpoints" def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc.VPCState) or isinstance(r, nixops.resources.vpc_route_table.VPCRouteTableState)} def realize_create_endpoint(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("vpc endpoint {} definition changed" " use --allow-recreate if you want to create a new one".format( self._state['endpointId'])) self.warn("vpc endpoint changed, recreating...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state["vpcId"] if not self._state.get('creationToken', None): self._state['creationToken'] = str(uuid.uuid4()) self.state = self.STARTING response = self.get_client().create_vpc_endpoint( ClientToken=self._state['creationToken'], ServiceName=config['serviceName'], VpcId=vpc_id) endpoint_id = response['VpcEndpoint']['VpcEndpointId'] with self.depl._db: self.state = self.UP self._state['endpointId'] = endpoint_id self._state['vpcId'] = vpc_id self._state['serviceName'] = config['serviceName'] def realize_modify_endpoint(self, allow_recreate): config = self.get_defn() old_rtbs = self._state.get('routeTableIds', []) new_rtbs = [] for rtb in config["routeTableIds"]: if rtb.startswith("res-"): res = self.depl.get_typed_resource(rtb[4:].split(".")[0], "vpc-route-table") new_rtbs.append(res._state['routeTableId']) else: new_rtbs.append(rtb) to_remove = [r for r in old_rtbs if r not in new_rtbs] to_add = [r for r in new_rtbs if r not in old_rtbs] edp_input = dict() edp_input['AddRouteTableIds'] = to_add edp_input['RemoveRouteTableIds'] = to_remove if config['policy'] is not None: edp_input['PolicyDocument'] edp_input['VpcEndpointId'] = self._state['endpointId'] self.get_client().modify_vpc_endpoint(**edp_input) with self.depl._db: self._state['policy'] = config['policy'] self._state['routeTableIds'] = new_rtbs def _destroy(self): if self.state != self.UP: return try: self.log("deleting vpc endpoint {}".format(self._state['endpointId'])) self.get_client().delete_vpc_endpoints(VpcEndpointIds=[self._state['endpointId']]) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'InvalidVpcEndpointId.NotFound': self.warn("vpc endpoint {} was already deleted".format(self._state['endpointId'])) else: raise e with self.depl._db: self.state = self.MISSING self._state['endpointId'] = None self._state['vpcId'] = None self._state['serviceName'] = None self._state['policy'] = None self._state['routeTableIds'] = None def destroy(self, wipe=False): self._destroy() return True
class AWSVPNConnectionState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a AWS VPN gateway.""" state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["vpnConnectionId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_vpn_conn = Handler( [ "region", "vpnGatewayId", "customerGatewayId", "staticRoutesOnly" ], handle=self.realize_create_vpn_conn, ) self.handle_tag_update = Handler( ["tags"], after=[self.handle_create_vpn_conn], handle=self.realize_update_tag, ) @classmethod def get_type(cls): return "aws-vpn-connection" def show_type(self): s = super(AWSVPNConnectionState, self).show_type() if self._state.get("region", None): s = "{0} [{1}]".format(s, self._state["region"]) return s @property def resource_id(self): return self._state.get("vpnConnectionId", None) def prefix_definition(self, attr): return {("resources", "awsVPNConnections"): attr} def get_defintion_prefix(self): return "resources.awsVPNConnections." def create_after(self, resources, defn): return { r for r in resources if isinstance( r, nixops_aws.resources.vpc_customer_gateway. VPCCustomerGatewayState) or isinstance( r, nixops_aws.resources.aws_vpn_gateway.AWSVPNGatewayState) } def realize_create_vpn_conn(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "vpn connection {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one". format(self._state["vpnConnectionId"])) self.warn("vpn connection definition changed, recreating ...") self._destroy() self._state["region"] = config["region"] customer_gtw_id = config["customerGatewayId"] if customer_gtw_id.startswith("res-"): res_vpc_customer_gw = self.depl.get_typed_resource( customer_gtw_id[4:].split(".")[0], "vpc-customer-gateway", VPCCustomerGatewayState, ) customer_gtw_id = res_vpc_customer_gw._state["customerGatewayId"] vpn_gateway_id = config["vpnGatewayId"] if vpn_gateway_id.startswith("res-"): res_vpn_gateway = self.depl.get_typed_resource( vpn_gateway_id[4:].split(".")[0], "aws-vpn-gateway", AWSVPNGatewayState) vpn_gateway_id = res_vpn_gateway._state["vpnGatewayId"] self.log( "creating vpn connection between customer gateway {0} and vpn gateway {1}" .format(customer_gtw_id, vpn_gateway_id)) vpn_connection = self.get_client().create_vpn_connection( CustomerGatewayId=customer_gtw_id, VpnGatewayId=vpn_gateway_id, Type="ipsec.1", Options={"StaticRoutesOnly": config["staticRoutesOnly"]}, ) vpn_conn_id = vpn_connection["VpnConnection"]["VpnConnectionId"] with self.depl._db: self.state = self.UP self._state["vpnConnectionId"] = vpn_conn_id self._state["vpnGatewayId"] = vpn_gateway_id self._state["customerGatewayId"] = customer_gtw_id self._state["staticRoutesOnly"] = config["staticRoutesOnly"] def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config["tags"] tags.update(self.get_common_tags()) self.get_client().create_tags( Resources=[self._state["vpnConnectionId"]], Tags=[{ "Key": k, "Value": tags[k] } for k in tags], ) def _destroy(self): if self.state == self.UP: self.log("deleting vpn connection {}".format( self._state["vpnConnectionId"])) try: self.get_client().delete_vpn_connection( VpnConnectionId=self._state["vpnConnectionId"]) except botocore.exceptions.ClientError as e: if e.response["Error"][ "Code"] == "InvalidVpnConnectionID.NotFound": self.warn("vpn connection {} was already deleted".format( self._state["vpnConnectionId"])) else: raise e with self.depl._db: self.state = self.MISSING self._state["vpnConnectionId"] = None self._state["vpnGatewayId"] = None self._state["customerGatewayId"] = None self._state["staticRoutesOnly"] = None def destroy(self, wipe=True): self._destroy() return True
class VPCRouteState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC route""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED TARGETS = ['egressOnlyInternetGatewayId', 'gatewayId', 'instanceId', 'natGatewayId', 'networkInterfaceId'] @classmethod def get_type(cls): return "vpc-route" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) keys = ['region', 'routeTableId', 'destinationCidrBlock', 'destinationIpv6CidrBlock', 'egressOnlyInternetGatewayId', 'gatewayId', 'instanceId', 'natGatewayId', 'networkInterfaceId'] self.handle_create_route = Handler(keys, handle=self.realize_create_route) def show_type(self): s = super(VPCRouteState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return (self._state.get('destinationCidrBlock', None) or self._state.get('destinationIpv6CidrBlock', None)) def prefix_definition(self, attr): return {('resources', 'vpcRoutes'): attr} def get_definition_prefix(self): return "resources.vpcRoutes." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc_route_table.VPCRouteTableState) or isinstance(r, nixops.resources.vpc_internet_gateway.VPCInternetGatewayState) or isinstance(r, nixops.resources.vpc_nat_gateway.VPCNatGatewayState)} def realize_create_route(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("route {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self.name)) self.warn("route definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] rtb_id = config['routeTableId'] if rtb_id.startswith("res-"): res = self.depl.get_typed_resource(rtb_id[4:].split(".")[0], "vpc-route-table") rtb_id = res._state['routeTableId'] route = dict() config = self.get_defn() num_targets = 0 for item in self.TARGETS: if config[item]: num_targets+=1 target = item if num_targets > 1: raise Exception("you should specify only 1 target from {}".format(str(self.TARGETS))) if (config['destinationCidrBlock'] is not None) and (config['destinationIpv6CidrBlock'] is not None): raise Exception("you can't set both destinationCidrBlock and destinationIpv6CidrBlock in one route") if config['destinationCidrBlock'] is not None: destination = 'destinationCidrBlock' if config['destinationIpv6CidrBlock'] is not None: destination = 'destinationIpv6CidrBlock' def retrieve_defn(option): cfg = config[option] if cfg.startswith("res-"): name = cfg[4:].split(".")[0] res_type = cfg.split(".")[1] attr = cfg.split(".")[2] if len(cfg.split(".")) > 2 else option res = self.depl.get_typed_resource(name, res_type) return res._state[attr] else: return cfg route['RouteTableId'] = rtb_id route[self.upper(target)] = retrieve_defn(target) route[self.upper(destination)] = config[destination] self.log("creating route {0} => {1} in route table {2}".format(retrieve_defn(target), config[destination], rtb_id)) self.get_client().create_route(**route) with self.depl._db: self.state = self.UP self._state[target] = route[self.upper(target)] self._state[destination] = config[destination] self._state['routeTableId'] = rtb_id def upper(self, option): return "%s%s" % (option[0].upper(), option[1:]) def _destroy(self): if self.state != self.UP: return destination = 'destinationCidrBlock' if ('destinationCidrBlock' in self._state.keys()) else 'destinationIpv6CidrBlock' self.log("deleting route to {0} from route table {1}".format(self._state[destination], self._state['routeTableId'])) try: args = dict() args[self.upper(destination)] = self._state[destination] args['RouteTableId'] = self._state['routeTableId'] self.get_client().delete_route(**args) except botocore.exceptions.ClientError as error: if error.response['Error']['Code'] == "InvalidRoute.NotFound": self.warn("route was already deleted") else: raise error with self.depl._db: self.state = self.MISSING self._state['routeTableId'] = None self._state[destination] = None for target in self.TARGETS: self._state[target] = None def destroy(self, wipe=False): self._destroy() return True
class VPCRouteTableAssociationState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC route table association""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['associationId'] @classmethod def get_type(cls): return "vpc-route-table-association" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_associate_route_table = Handler(['region', 'routeTableId', 'subnetId'], handle=self.realize_associate_route_table) def show_type(self): s = super(VPCRouteTableAssociationState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('associationId', None) def prefix_definition(self, attr): return {('resources', 'vpcRouteTableAssociations'): attr} def get_physical_spec(self): return { 'associationId': self._state.get('associationId', None) } def get_definition_prefix(self): return "resources.vpcRouteTableAssociations." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc_route_table.VPCRouteTableState)} def realize_associate_route_table(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("route table association {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self._state['associationId'])) self.warn("route table association definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] route_table_id = config['routeTableId'] if route_table_id.startswith("res-"): res = self.depl.get_typed_resource(route_table_id[4:].split(".")[0], "vpc-route-table") route_table_id = res._state['routeTableId'] subnet_id = config['subnetId'] if subnet_id.startswith("res-"): res = self.depl.get_typed_resource(subnet_id[4:].split(".")[0], "vpc-subnet") subnet_id = res._state['subnetId'] self.log("associating route table {0} to subnet {1}".format(route_table_id, subnet_id)) association = self.get_client().associate_route_table(RouteTableId=route_table_id, SubnetId=subnet_id) with self.depl._db: self.state = self.UP self._state['routeTableId'] = route_table_id self._state['subnetId'] = subnet_id self._state['associationId'] = association['AssociationId'] def _destroy(self): if self.state != self.UP: return self.log("disassociating route table {0} from subnet {1}".format(self._state['routeTableId'], self._state['subnetId'])) try: self.get_client().disassociate_route_table(AssociationId=self._state['associationId']) except botocore.exceptions.ClientError as error: if error.response['Error']['Code'] == "InvalidAssociationID.NotFound": self.warn("route table {} was already deleted".format(self._state['associationId'])) else: raise error with self.depl._db: self.state = self.MISSING self._state['routeTableId'] = None self._state['subnetId'] = None self._state['associationId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCEgressOnlyInternetGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC egress only internet gateway.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["egressOnlyInternetGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_igw = Handler(['region', 'vpcId'], handle=self.realize_create_gtw) @classmethod def get_type(cls): return "vpc-egress-only-internet-gateway" def show_type(self): s = super(VPCEgressOnlyInternetGatewayState, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self._state.get('egressOnlyInternetGatewayId', None) def prefix_definition(self, attr): return {('resources', 'vpcEgressOnlyInternetGateways'): attr} def get_defintion_prefix(self): return "resources.vpcEgressOnlyInternetGateways." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc.VPCState) or isinstance(r, nixops.resources.elastic_ip.ElasticIPState)} def realize_create_gtw(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("egress only internet gateway {} defintion changed" " use --allow-recreate if you want to create a new one".format( self._state['egressOnlyInternetGatewayId'])) self.warn("egress only internet gateway changed, recreating...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating egress only internet gateway in region {0}, vpc {1}".format(self._state['region'], vpc_id)) response = self.get_client().create_egress_only_internet_gateway(VpcId=vpc_id) igw_id = response['EgressOnlyInternetGateway']['EgressOnlyInternetGatewayId'] with self.depl._db: self.state = self.UP self._state['region'] = config['region'] self._state['vpcId'] = vpc_id self._state['egressOnlyInternetGatewayId'] = igw_id def _destroy(self): if self.state != self.UP: return self.log("deleting egress only internet gateway {0}".format(self._state['egressOnlyInternetGatewayId'])) self.get_client().delete_egress_only_internet_gateway(EgressOnlyInternetGatewayId=self._state['egressOnlyInternetGatewayId']) with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['vpcId'] = None self._state['egressOnlyInternetGatewayId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCNetworkAclstate(nixops.resources.DiffEngineResourceState, EC2CommonState): """state of a vpc Network ACL.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['networkAclId'] @classmethod def get_type(cls): return "vpc-network-acl" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.network_acl_id = self._state.get('networkAclId', None) self.handle_create_network_acl = Handler(['region', 'vpcId'], handle=self.realize_create_network_acl) self.handle_entries = Handler(['entries'], after=[self.handle_create_network_acl] , handle=self.realize_entries_change) self.handle_subnet_association = Handler(['subnetIds'], after=[self.handle_create_network_acl] , handle=self.realize_subnets_change) self.handle_tag_update = Handler(['tags'], after=[self.handle_create_network_acl], handle=self.realize_update_tag) def show_type(self): s = super(VPCNetworkAclstate, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self.network_acl_id def prefix_definition(self, attr): return {('resources', 'vpcNetworkAcls'): attr} def get_definition_prefix(self): return "resources.vpcNetworkAcls." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixopsaws.resources.vpc.VPCState) or isinstance(r, nixopsaws.resources.vpc_subnet.VPCSubnetState)} def realize_create_network_acl(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("network ACL {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self.network_acl_id)) self.warn("network ACL definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating network ACL in vpc {}".format(vpc_id)) response = self.get_client().create_network_acl(VpcId=vpc_id) self.network_acl_id = response['NetworkAcl']['NetworkAclId'] with self.depl._db: self.state = self.UP self._state['vpcId'] = vpc_id self._state['networkAclId'] = self.network_acl_id def realize_entries_change(self, allow_recreate): config = self.get_defn() old_entries = self._state.get('entries', []) new_entries = config['entries'] to_remove = [e for e in old_entries if e not in new_entries] to_create = [e for e in new_entries if e not in old_entries] for entry in to_remove: try: self.get_client().delete_network_acl_entry(NetworkAclId=self.network_acl_id, RuleNumber=entry['ruleNumber'], Egress=entry['egress']) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidNetworkAclEntry.NotFound": self.warn("rule {0} was already deleted from network ACL {1}".format(entry['ruleNumber'], self.network_acl_id)) else: raise e for entry in to_create: rule = self.process_rule_entry(entry) self.get_client().create_network_acl_entry(**rule) with self.depl._db: self._state['entries'] = config['entries'] def realize_subnets_change(self, allow_recreate): config = self.get_defn() old_subnets = self._state.get('subnetIds', []) new_subnets = [] for s in config['subnetIds']: if s.startswith("res-"): res = self.depl.get_typed_resource(s[4:].split(".")[0], "vpc-subnet") new_subnets.append(res._state['subnetId']) else: new_subnets.append(s) vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] subnets_to_remove = [s for s in old_subnets if s not in new_subnets] subnets_to_add = [s for s in new_subnets if s not in old_subnets] default_network_acl = self.get_default_network_acl(vpc_id) for subnet in subnets_to_remove: association_id = self.get_network_acl_association(subnet) self.log("associating subnet {0} to default network acl {1}".format(subnet, default_network_acl)) self.get_client().replace_network_acl_association(AssociationId=association_id, NetworkAclId=default_network_acl) for subnet in subnets_to_add: association_id = self.get_network_acl_association(subnet) self.log("associating subnet {0} to network acl {1}".format(subnet, self.network_acl_id)) self.get_client().replace_network_acl_association(AssociationId=association_id, NetworkAclId=self.network_acl_id) with self.depl._db: self._state['subnetIds'] = new_subnets def get_default_network_acl(self, vpc_id): response = self.get_client().describe_network_acls(Filters=[{ "Name": "default", "Values": [ "true" ] }, { "Name": "vpc-id", "Values": [ vpc_id ]}]) return response['NetworkAcls'][0]['NetworkAclId'] def get_network_acl_association(self, subnet_id): response = self.get_client().describe_network_acls(Filters=[{"Name": "association.subnet-id", "Values":[ subnet_id ]}]) for association in response['NetworkAcls'][0]['Associations']: if association['SubnetId'] == subnet_id: return association['NetworkAclAssociationId'] def process_rule_entry(self, entry): rule = dict() rule['NetworkAclId'] = self.network_acl_id rule['Protocol'] = entry['protocol'] rule['RuleNumber'] = entry['ruleNumber'] rule['RuleAction'] = entry['ruleAction'] rule['Egress'] = entry['egress'] if entry['cidrBlock'] is not None: rule['CidrBlock'] = entry['cidrBlock'] if entry['ipv6CidrBlock'] is not None: rule['Ipv6CidrBlock'] = entry['ipv6CidrBlock'] if entry['icmpCode'] and entry['icmpType']: rule['IcmpTypeCode'] = {"Type": entry['icmpType'], "Code": entry['icmpCode']} if entry['fromPort'] and entry['toPort']: rule['PortRange'] = { "From": entry['fromPort'], "To": entry['toPort'] } return rule def _destroy(self): if self.state != self.UP: return try: subnets = self._state.get('subnetIds', []) default_network_acl = self.get_default_network_acl(self._state['vpcId']) for subnet in subnets: association_id = self.get_network_acl_association(subnet) self.log("associating subnet {0} to default network acl {1}".format(subnet, default_network_acl)) self.get_client().replace_network_acl_association(AssociationId=association_id, NetworkAclId=default_network_acl) self.log("deleting network acl {}".format(self.network_acl_id)) self.get_client().delete_network_acl(NetworkAclId=self.network_acl_id) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'InvalidNetworkAclID.NotFound': self.warn("network ACL {} was already deleted".format(self.network_acl_id)) else: raise e with self.depl._db: self.state = self.MISSING self._state['networkAclId'] = None self._state['region'] = None self._state['vpcId'] = None self._state['entries'] = None def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['networkAclId']], Tags=[{"Key": k, "Value": tags[k]} for k in tags]) def destroy(self, wipe=False): self._destroy() return True
class VPCNetworkInterfaceAttachmentState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC network interface attachment""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['attachmentId'] @classmethod def get_type(cls): return "vpc-network-interface-attachment" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_eni_attachment = Handler(['region', 'networkInterfaceId', 'instanceId', 'deviceIndex' ], handle=self.realize_create_eni_attachment) def show_type(self): s = super(VPCNetworkInterfaceAttachmentState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('attachmentId', None) def prefix_definition(self, attr): return {('resources', 'vpcNetworkInterfaceAttachments'): attr} def get_physical_spec(self): return { 'attachmentId': self._state.get('attachmentId', None) } def get_definition_prefix(self): return "resources.vpcNetworkInterfaceAttachments." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc_network_interface.VPCNetworkInterfaceState) or isinstance(r, nixops.backends.ec2.EC2State)} def ensure_state_up(self): config = self.get_defn() self._state["region"] = config["region"] if self._state.get('attachmentId', None): if self.state != self.UP: self.wait_for_eni_attachment(self._state['networkInterfaceId']) def wait_for_eni_attachment(self, eni_id): while True: response = self.get_client().describe_network_interface_attribute( Attribute='attachment', NetworkInterfaceId=eni_id) if response.get('Attachment', None): if response['Attachment']['Status'] == 'attached': break elif response['Attachment']['Status'] != "attaching": raise Exception("eni attachment {0} in an unexpected state {1}".format( eni_id, response['Attachment']['Status'])) self.log_continue(".") time.sleep(1) else: raise Exception("eni {} doesn't have any attachment {}".format(eni_id)) self.log_end(" done") with self.depl._db: self.state = self.UP def realize_create_eni_attachment(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("network interface attachment {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self._state['attachmentId'])) self.warn("network interface attachment definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] vm_id = config['instanceId'] if vm_id.startswith("res-"): res = self.depl.get_typed_resource(vm_id[4:].split(".")[0], "ec2") vm_id = res.vm_id eni_id = config['networkInterfaceId'] if eni_id.startswith("res-"): res = self.depl.get_typed_resource(eni_id[4:].split(".")[0], "vpc-network-interface") eni_id = res._state['networkInterfaceId'] self.log("attaching network interface {0} to instance {1}".format(eni_id, vm_id)) eni_attachment = self.get_client().attach_network_interface( DeviceIndex=config['deviceIndex'], InstanceId=vm_id, NetworkInterfaceId=eni_id) with self.depl._db: self.state = self.STARTING self._state['attachmentId'] = eni_attachment['AttachmentId'] self._state['instanceId'] = vm_id self._state['deviceIndex'] = config['deviceIndex'] self._state['networkInterfaceId'] = eni_id self.wait_for_eni_attachment(eni_id) def wait_for_eni_detachment(self): self.log("waiting for eni attachment {0} to be detached from {1}".format(self._state['attachmentId'], self._state["instanceId"])) while True: response = self.get_client().describe_network_interface_attribute( Attribute='attachment', NetworkInterfaceId=self._state['networkInterfaceId']) if response.get('Attachment', None): if response['Attachment']['Status'] == 'detached': break elif response['Attachment']['Status'] != "detaching": raise Exception("eni attachment {0} in an unexpected state {1}".format( eni_id, response['Attachment']['Status'])) self.log_continue(".") time.sleep(1) else: break self.log_end(" done") def _destroy(self): if self.state == self.UP: self.log("detaching vpc network interface attachment {}".format(self._state['attachmentId'])) try: self.get_client().detach_network_interface(AttachmentId=self._state['attachmentId'], Force=True) with self.depl._db: self.state = self.STOPPING except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidAttachmentID.NotFound": self.warn("network interface attachment {} was already detached".format(self._state['attachmentId'])) else: raise e if self.state == self.STOPPING: self.wait_for_eni_detachment() with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['attachmentId'] = None self._state['instanceId'] = None self._state['deviceIndex'] = None self._state['networkInterfaceId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCNetworkInterfaceState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC network interface""" state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + [ "networkInterfaceId" ] @classmethod def get_type(cls): return "vpc-network-interface" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get("region", None) self.handle_create_eni = Handler( [ "region", "subnetId", "primaryPrivateIpAddress", "privateIpAddresses", "secondaryPrivateIpAddressCount", ], handle=self.realize_create_eni, ) self.handle_modify_eni_attrs = Handler( ["description", "securityGroups", "sourceDestCheck"], handle=self.realize_modify_eni_attrs, after=[self.handle_create_eni], ) self.handle_tag_update = Handler(["tags"], after=[self.handle_create_eni], handle=self.realize_update_tag) def show_type(self): s = super(VPCNetworkInterfaceState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get("networkInterfaceId", None) def prefix_definition(self, attr): return {("resources", "vpcNetworkInterfaces"): attr} def get_physical_spec(self): return { "networkInterfaceId": self._state.get("networkInterfaceId", None) } def get_definition_prefix(self): return "resources.vpcNetworkInterfaces." def create_after(self, resources, defn): return { r for r in resources if isinstance(r, vpc_subnet.VPCSubnetState) } def realize_create_eni(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "network interface {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one". format(self._state["networkInterfaceId"])) self.warn("network interface definition changed, recreating ...") self._destroy() self._state["region"] = config["region"] eni_input = self.network_interface_input(config) self.log("creating vpc network interface under {}".format( eni_input["SubnetId"])) response = self.get_client().create_network_interface(**eni_input) eni = response["NetworkInterface"] def split_ips(eni_ips): seconday = [] for ip in eni_ips: if ip["Primary"]: primary = ip["PrivateIpAddress"] else: seconday.append(ip["PrivateIpAddress"]) return primary, seconday primary, secondary = split_ips(eni["PrivateIpAddresses"]) with self.depl._db: self.state = self.UP self._state["subnetId"] = eni_input["SubnetId"] self._state["networkInterfaceId"] = eni["NetworkInterfaceId"] self._state["primaryPrivateIpAddress"] = primary self._state["privateIpAddresses"] = secondary self._state["secondaryPrivateIpAddressCount"] = config[ "secondaryPrivateIpAddressCount"] def network_interface_input(self, config): subnet_id = config["subnetId"] if subnet_id.startswith("res-"): res = self.depl.get_typed_resource(subnet_id[4:].split(".")[0], "vpc-subnet", VPCSubnetState) subnet_id = res._state["subnetId"] groups = [] for grp in config["securityGroups"]: if grp.startswith("res-"): res_sg = self.depl.get_typed_resource(grp[4:].split(".")[0], "ec2-security-group", EC2SecurityGroupState) assert res_sg.vpc_id groups.append(res_sg.security_group_id) else: groups.append(grp) primary_ip = config["primaryPrivateIpAddress"] secondary_private_ips = config["privateIpAddresses"] secondary_ip_count = config["secondaryPrivateIpAddressCount"] if (primary_ip and len(secondary_private_ips) > 0) and secondary_ip_count: raise Exception( "you can't set privateIpAddresses/primaryPrivateIpAddress options together" " with secondaryPrivateIpAddressCount") ips = [] if primary_ip: ips.append({"Primary": True, "PrivateIpAddress": primary_ip}) if len(secondary_private_ips) > 0: for ip in secondary_private_ips: ips.append({"Primary": False, "PrivateIpAddress": ip}) cfg = dict() cfg["Description"] = config["description"] cfg["Groups"] = groups cfg["SubnetId"] = subnet_id if not secondary_ip_count: cfg["PrivateIpAddresses"] = ips else: cfg["secondaryPrivateIpAddressCount"] = secondary_ip_count return cfg def realize_modify_eni_attrs(self, allow_recreate): config = self.get_defn() self.log("applying network interface attribute changes") self.get_client().modify_network_interface_attribute( NetworkInterfaceId=self._state["networkInterfaceId"], Description={"Value": config["description"]}, ) groups = [] for grp in config["securityGroups"]: if grp.startswith("res-"): res = self.depl.get_typed_resource(grp[4:].split(".")[0], "ec2-security-group", EC2SecurityGroupState) assert res.vpc_id groups.append(res.security_group_id) else: groups.append(grp) if len(groups) >= 1: self.get_client().modify_network_interface_attribute( NetworkInterfaceId=self._state["networkInterfaceId"], Groups=groups) self.get_client().modify_network_interface_attribute( NetworkInterfaceId=self._state["networkInterfaceId"], SourceDestCheck={"Value": config["sourceDestCheck"]}, ) with self.depl._db: self._state["description"] = config["description"] self._state["securityGroups"] = groups self._state["sourceDestCheck"] = config["sourceDestCheck"] def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config["tags"] tags.update(self.get_common_tags()) self.get_client().create_tags( Resources=[self._state["networkInterfaceId"]], Tags=[{ "Key": k, "Value": tags[k] } for k in tags], ) def _destroy(self): if self.state != self.UP: return self.log("deleting vpc network interface {}".format( self._state["networkInterfaceId"])) try: self.get_client().delete_network_interface( NetworkInterfaceId=self._state["networkInterfaceId"]) except botocore.exceptions.ClientError as e: if e.response["Error"][ "Code"] == "InvalidNetworkInterfaceID.NotFound": self.warn("network interface {} was already deleted".format( self._state["networkInterfaceId"])) else: raise e with self.depl._db: self.state = self.MISSING self._state["networkInterfaceId"] = None self._state["primaryPrivateIpAddress"] = None self._state["privateIpAddresses"] = None self._state["secondaryPrivateIpAddressCount"] = None def destroy(self, wipe=False): self._destroy() return True
class VPCCustomerGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC customer gateway.""" definition_type = VPCCustomerGatewayDefinition state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["customerGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_customer_gtw = Handler( ["region", "publicIp", "bgpAsn", "type"], handle=self.realize_create_customer_gtw, ) self.handle_tag_update = Handler( ["tags"], after=[self.handle_create_customer_gtw], handle=self.realize_update_tag, ) @classmethod def get_type(cls): return "vpc-customer-gateway" def show_type(self): s = super(VPCCustomerGatewayState, self).show_type() if self._state.get("region", None): s = "{0} [{1}]".format(s, self._state["region"]) return s @property def resource_id(self): return self._state.get("customerGatewayId", None) def prefix_definition(self, attr): return {("resources", "vpcCustomerGateways"): attr} def get_defintion_prefix(self): return "resources.vpcCustomerGateways." def realize_create_customer_gtw(self, allow_recreate): config: VPCCustomerGatewayDefinition = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "customer gateway {} defintion changed" " use --allow-recreate if you want to create a new one". format(self._state["customerGatewayId"])) self.warn("customer gateway changed, recreating...") self._destroy() self._state["region"] = config.config.region self.log("creating customer gateway") response = self.get_client().create_customer_gateway( BgpAsn=config.config.bgpAsn, PublicIp=config.config.publicIp, Type=config.config.type, ) customer_gtw_id = response["CustomerGateway"]["CustomerGatewayId"] with self.depl._db: self.state = self.STARTING waiter = self.get_client().get_waiter("customer_gateway_available") waiter.wait(CustomerGatewayIds=[customer_gtw_id]) with self.depl._db: self.state = self.UP self._state["region"] = config.config.region self._state["customerGatewayId"] = customer_gtw_id self._state["bgpAsn"] = config.config.bgpAsn self._state["publicIp"] = config.config.publicIp self._state["type"] = config.config.type def realize_update_tag(self, allow_recreate): config: VPCCustomerGatewayDefinition = self.get_defn() tags = {k: v for k, v in config.config.tags.items()} tags.update(self.get_common_tags()) self.get_client().create_tags( Resources=[self._state["customerGatewayId"]], Tags=[{ "Key": k, "Value": tags[k] } for k in tags], ) def _destroy(self): if self.state != self.UP: return self.log("deleting customer gateway {}".format( self._state["customerGatewayId"])) try: self.get_client().delete_customer_gateway( CustomerGatewayId=self._state["customerGatewayId"]) except botocore.exceptions.ClientError as e: if e.response["Error"][ "Code"] == "InvalidCustomerGatewayID.NotFound": self.warn("customer gateway {} was already deleted".format( self._state["customerGatewayId"])) else: raise e # TODO wait for customer gtw to be deleted with self.depl._db: self.state = self.MISSING self._state["region"] = None self._state["customerGatewayId"] = None self._state["bgpAsn"] = None self._state["publicIp"] = None self._state["type"] = None def destroy(self, wipe=False): self._destroy() return True
class AWSVPNGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a AWS VPN gateway.""" definition_type = AWSVPNGatewayDefinition state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ["vpnGatewayId"] def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.handle_create_vpn_gtw = Handler( ["region", "zone", "vpcId"], handle=self.realize_create_vpn_gtw) self.handle_tag_update = Handler(["tags"], after=[self.handle_create_vpn_gtw], handle=self.realize_update_tag) @classmethod def get_type(cls): return "aws-vpn-gateway" def show_type(self): s = super(AWSVPNGatewayState, self).show_type() if self._state.get("region", None): s = "{0} [{1}]".format(s, self._state["region"]) return s @property def resource_id(self): return self._state.get("vpnGatewayId", None) def prefix_definition(self, attr): return {("resources", "awsVPNGateways"): attr} def get_defintion_prefix(self): return "resources.awsVPNGateways." def create_after(self, resources, defn): return { r for r in resources if isinstance(r, nixops_aws.resources.vpc.VPCState) } def realize_create_vpn_gtw(self, allow_recreate): config: AWSVPNGatewayDefinition = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "VPN gateway {} defintion changed" " use --allow-recreate if you want to create a new one". format(self._state["vpnGatewayId"])) self.warn("VPN gateway changed, recreating...") self._destroy() self._state["region"] = config.config.region vpc_id = config.config.vpcId if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc", VPCState) vpc_id = res._state["vpcId"] self.log("creating VPN gateway in zone {}".format(config.config.zone)) response = self.get_client().create_vpn_gateway( AvailabilityZone=config.config.zone, Type="ipsec.1") vpn_gtw_id = response["VpnGateway"]["VpnGatewayId"] self.log("attaching vpn gateway {0} to vpc {1}".format( vpn_gtw_id, vpc_id)) self.get_client().attach_vpn_gateway(VpcId=vpc_id, VpnGatewayId=vpn_gtw_id) # TODO wait for the attchement state with self.depl._db: self.state = self.UP self._state["vpnGatewayId"] = vpn_gtw_id self._state["vpcId"] = vpc_id self._state["zone"] = config.config.zone def realize_update_tag(self, allow_recreate): config: AWSVPNGatewayDefinition = self.get_defn() tags = {k: v for k, v in config.config.tags.items()} tags.update(self.get_common_tags()) self.get_client().create_tags( Resources=[self._state["vpnGatewayId"]], Tags=[{ "Key": k, "Value": tags[k] } for k in tags], ) def _destroy(self): if self.state != self.UP: return self.log("detaching vpn gateway {0} from vpc {1}".format( self._state["vpnGatewayId"], self._state["vpcId"])) try: self.get_client().detach_vpn_gateway( VpcId=self._state["vpcId"], VpnGatewayId=self._state["vpnGatewayId"]) except botocore.exceptions.ClientError as e: if e.response["Error"][ "Code"] == "InvalidVpnGatewayAttachment.NotFound": self.warn( "VPN gateway '{0}' attachment with VPC '{1}' is invalid". format(self._state["vpnGatewayId"], self._state["vpcId"])) else: raise e # TODO delete VPN connections associated with this VPN gtw self.log("deleting vpn gateway {}".format(self._state["vpnGatewayId"])) try: self.get_client().delete_vpn_gateway( VpnGatewayId=self._state["vpnGatewayId"]) except botocore.exceptions.ClientError as e: if e.response["Error"]["Code"] == "InvalidVpnGatewayID.NotFound": self.warn("VPN gateway {} was already deleted".format( self._state["vpnGatewayId"])) else: raise e with self.depl._db: self.state = self.MISSING self._state["region"] = None self._state["vpnGatewayId"] = None self._state["vpcId"] = None self._state["zone"] = None def destroy(self, wipe=False): self._destroy() return True
class VPCRouteTableAssociationState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC route table association""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['associationId'] @classmethod def get_type(cls): return "vpc-route-table-association" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_associate_route_table = Handler(['region', 'routeTableId', 'subnetId'], handle=self.realize_associate_route_table) def show_type(self): s = super(VPCRouteTableAssociationState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('associationId', None) def prefix_definition(self, attr): return {('resources', 'vpcRouteTableAssociations'): attr} def get_physical_spec(self): return { 'associationId': self._state.get('associationId', None) } def get_definition_prefix(self): return "resources.vpcRouteTableAssociations." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixopsaws.resources.vpc_route_table.VPCRouteTableState)} def realize_associate_route_table(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("route table association {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self._state['associationId'])) self.warn("route table association definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] route_table_id = config['routeTableId'] if route_table_id.startswith("res-"): res = self.depl.get_typed_resource(route_table_id[4:].split(".")[0], "vpc-route-table") route_table_id = res._state['routeTableId'] subnet_id = config['subnetId'] if subnet_id.startswith("res-"): res = self.depl.get_typed_resource(subnet_id[4:].split(".")[0], "vpc-subnet") subnet_id = res._state['subnetId'] self.log("associating route table {0} to subnet {1}".format(route_table_id, subnet_id)) association = self.get_client().associate_route_table(RouteTableId=route_table_id, SubnetId=subnet_id) with self.depl._db: self.state = self.UP self._state['routeTableId'] = route_table_id self._state['subnetId'] = subnet_id self._state['associationId'] = association['AssociationId'] def _destroy(self): if self.state != self.UP: return self.log("disassociating route table {0} from subnet {1}".format(self._state['routeTableId'], self._state['subnetId'])) try: self.get_client().disassociate_route_table(AssociationId=self._state['associationId']) except botocore.exceptions.ClientError as error: if error.response['Error']['Code'] == "InvalidAssociationID.NotFound": self.warn("route table {} was already deleted".format(self._state['associationId'])) else: raise error with self.depl._db: self.state = self.MISSING self._state['routeTableId'] = None self._state['subnetId'] = None self._state['associationId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCNatGatewayState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC NAT gateway""" state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + [ 'natGatewayId', 'creationToken' ] @classmethod def get_type(cls): return "vpc-nat-gateway" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_gtw = Handler( ['region', 'subnetId', 'allocationId'], handle=self.realize_create_gtw) self.nat_gtw_id = self._state.get('natGatewayId', None) def show_type(self): s = super(VPCNatGatewayState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('natGatewayId', None) def prefix_definition(self, attr): return {('resources', 'vpcNatGateways'): attr} def get_physical_spec(self): return {'natGatewayId': self._state.get('natGatewayId', None)} def get_definition_prefix(self): return "resources.vpcNatGateways." def create_after(self, resources, defn): return { r for r in resources if isinstance(r, nixopsaws.resources.vpc_subnet.VPCSubnetState) or isinstance(r, nixopsaws.resources.elastic_ip.ElasticIPState) } def realize_create_gtw(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "nat gateway {} defintion changed" " use --allow-recreate if you want to create a new one". format(self._state['natGatewayId'])) self.warn("nat gateway changed, recreating...") self._destroy() self._state['region'] = config['region'] subnet_id = config['subnetId'] allocation_id = config['allocationId'] if allocation_id.startswith("res-"): res = self.depl.get_typed_resource(allocation_id[4:].split(".")[0], "elastic-ip") allocation_id = res.allocation_id if subnet_id.startswith("res-"): res = self.depl.get_typed_resource(subnet_id[4:].split(".")[0], "vpc-subnet") subnet_id = res._state['subnetId'] if not self._state.get('creationToken', None): self._state['creationToken'] = str(uuid.uuid4()) self.state = self.STARTING response = self.get_client().create_nat_gateway( ClientToken=self._state['creationToken'], AllocationId=allocation_id, SubnetId=subnet_id) gtw_id = response['NatGateway']['NatGatewayId'] with self.depl._db: self.state = self.UP self._state['subnetId'] = subnet_id self._state['allocationId'] = allocation_id self._state['natGatewayId'] = gtw_id def wait_for_nat_gtw_deletion(self): self.log("waiting for nat gateway {0} to be deleted".format( self._state['natGatewayId'])) while True: try: response = self.get_client().describe_nat_gateways( NatGatewayIds=[self._state['natGatewayId']]) except botocore.exceptions.ClientError as e: if e.response['Error'][ 'Code'] == "InvalidNatGatewayID.NotFound" or e.response[ 'Error']['Code'] == "NatGatewayNotFound": self.warn("nat gateway {} was already deleted".format( self._state['natGatewayId'])) break else: raise if len(response['NatGateways']) == 1: if response['NatGateways'][0]['State'] == "deleted": break elif response['NatGateways'][0]['State'] != "deleting": raise Exception( "nat gateway {0} in an unexpected state {1}".format( self._state['natGatewayId'], response['NatGateways'][0]['State'])) self.log_continue(".") time.sleep(1) else: break self.log_end(" done") def _destroy(self): if self.state == self.UP: self.log("deleting vpc NAT gateway {}".format( self._state['natGatewayId'])) try: self.get_client().delete_nat_gateway( NatGatewayId=self._state['natGatewayId']) with self.depl._db: self.state = self.STOPPING except botocore.exceptions.ClientError as e: if e.response['Error'][ 'Code'] == "InvalidNatGatewayID.NotFound" or e.response[ 'Error']['Code'] == "NatGatewayNotFound": self.warn("nat gateway {} was already deleted".format( self._state['natGatewayId'])) else: raise e if self.state == self.STOPPING: self.wait_for_nat_gtw_deletion() with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['subnetId'] = None self._state['allocationId'] = None self._state['natGatewayId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCRouteTableState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC route table""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['routeTableId'] @classmethod def get_type(cls): return "vpc-route-table" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_route_table = Handler(['region', 'vpcId'], handle=self.realize_create_route_table) self.handle_propagate_vpn_gtws = Handler( ['propagatingVgws'], handle=self.realize_propagate_vpn_gtws, after=[self.handle_create_route_table]) self.handle_tag_update = Handler(['tags'], after=[self.handle_create_route_table], handle=self.realize_update_tag) def show_type(self): s = super(VPCRouteTableState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('routeTableId', None) def prefix_definition(self, attr): return {('resources', 'vpcRouteTables'): attr} def get_physical_spec(self): return { 'routeTableId': self._state.get('routeTableId', None) } def get_definition_prefix(self): return "resources.vpcRouteTables." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc.VPCState) or isinstance(r, nixops.resources.vpc_subnet.VPCSubnetState)} def realize_create_route_table(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("route table {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self._state['routeTableId'])) self.warn("route table definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating route table in vpc {}".format(vpc_id)) route_table = self.get_client().create_route_table(VpcId=vpc_id) with self.depl._db: self.state = self.UP self._state['vpcId'] = vpc_id self._state['routeTableId'] = route_table['RouteTable']['RouteTableId'] def realize_propagate_vpn_gtws(self, allow_recreate): config = self.get_defn() old_vgws = self._state.get('propagatingVgws', []) new_vgws = [] for vgw in config['propagatingVgws']: if vgw.startswith("res-"): res = self.depl.get_typed_resource(vgw[4:].split(".")[0], "aws-vpn-gateway") new_vgws.append(res._state['vpnGatewayId']) else: new_vgws.append(vgw) to_disable = [r for r in old_vgws if r not in new_vgws] to_enable = [r for r in new_vgws if r not in old_vgws] for vgw in to_disable: self.log("disabling virtual gateway route propagation for {}".format(vgw)) self.get_client().disable_vgw_route_propagation( GatewayId=vgw, RouteTableId=self._state['routeTableId']) for vgw in to_enable: self.log("enabling virtual gateway route propagation for {}".format(vgw)) self.get_client().enable_vgw_route_propagation( GatewayId=vgw, RouteTableId=self._state['routeTableId']) with self.depl._db: self._state['propagatingVgws'] = new_vgws def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['routeTableId']], Tags=[{"Key": k, "Value": tags[k]} for k in tags]) def _destroy(self): if self.state != self.UP: return self.log("deleting route table {}".format(self._state['routeTableId'])) try: self.get_client().delete_route_table(RouteTableId=self._state['routeTableId']) except botocore.exceptions.ClientError as error: if error.response['Error']['Code'] == "InvalidRouteTableID.NotFound": self.warn("route table {} was already deleted".format(self._state['routeTableId'])) else: raise error with self.depl._db: self.state = self.MISSING self._state['vpcId'] = None self._state['routeTableId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCRouteTableState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC route table""" state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['routeTableId'] @classmethod def get_type(cls): return "vpc-route-table" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_route_table = Handler( ['region', 'vpcId'], handle=self.realize_create_route_table) self.handle_propagate_vpn_gtws = Handler( ['propagatingVgws'], handle=self.realize_propagate_vpn_gtws, after=[self.handle_create_route_table]) self.handle_tag_update = Handler( ['tags'], after=[self.handle_create_route_table], handle=self.realize_update_tag) def show_type(self): s = super(VPCRouteTableState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('routeTableId', None) def prefix_definition(self, attr): return {('resources', 'vpcRouteTables'): attr} def get_physical_spec(self): return {'routeTableId': self._state.get('routeTableId', None)} def get_definition_prefix(self): return "resources.vpcRouteTables." def create_after(self, resources, defn): return { r for r in resources if isinstance(r, nixops.resources.vpc.VPCState) or isinstance(r, nixops.resources.vpc_subnet.VPCSubnetState) } def realize_create_route_table(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "route table {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one". format(self._state['routeTableId'])) self.warn("route table definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating route table in vpc {}".format(vpc_id)) route_table = self.get_client().create_route_table(VpcId=vpc_id) with self.depl._db: self.state = self.UP self._state['vpcId'] = vpc_id self._state['routeTableId'] = route_table['RouteTable'][ 'RouteTableId'] def realize_propagate_vpn_gtws(self, allow_recreate): config = self.get_defn() old_vgws = self._state.get('propagatingVgws', []) new_vgws = [] for vgw in config['propagatingVgws']: if vgw.startswith("res-"): res = self.depl.get_typed_resource(vgw[4:].split(".")[0], "aws-vpn-gateway") new_vgws.append(res._state['vpnGatewayId']) else: new_vgws.append(vgw) to_disable = [r for r in old_vgws if r not in new_vgws] to_enable = [r for r in new_vgws if r not in old_vgws] for vgw in to_disable: self.log( "disabling virtual gateway route propagation for {}".format( vgw)) self.get_client().disable_vgw_route_propagation( GatewayId=vgw, RouteTableId=self._state['routeTableId']) for vgw in to_enable: self.log( "enabling virtual gateway route propagation for {}".format( vgw)) self.get_client().enable_vgw_route_propagation( GatewayId=vgw, RouteTableId=self._state['routeTableId']) with self.depl._db: self._state['propagatingVgws'] = new_vgws def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['routeTableId']], Tags=[{ "Key": k, "Value": tags[k] } for k in tags]) def _destroy(self): if self.state != self.UP: return self.log("deleting route table {}".format(self._state['routeTableId'])) try: self.get_client().delete_route_table( RouteTableId=self._state['routeTableId']) except botocore.exceptions.ClientError as error: if error.response['Error'][ 'Code'] == "InvalidRouteTableID.NotFound": self.warn("route table {} was already deleted".format( self._state['routeTableId'])) else: raise error with self.depl._db: self.state = self.MISSING self._state['vpcId'] = None self._state['routeTableId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCNetworkInterfaceAttachmentState( nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC network interface attachment""" state = nixops.util.attr_property( "state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['attachmentId'] @classmethod def get_type(cls): return "vpc-network-interface-attachment" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_eni_attachment = Handler( ['region', 'networkInterfaceId', 'instanceId', 'deviceIndex'], handle=self.realize_create_eni_attachment) def show_type(self): s = super(VPCNetworkInterfaceAttachmentState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('attachmentId', None) def prefix_definition(self, attr): return {('resources', 'vpcNetworkInterfaceAttachments'): attr} def get_physical_spec(self): return {'attachmentId': self._state.get('attachmentId', None)} def get_definition_prefix(self): return "resources.vpcNetworkInterfaceAttachments." def create_after(self, resources, defn): return { r for r in resources if isinstance( r, nixopsaws.resources.vpc_network_interface. VPCNetworkInterfaceState) or isinstance(r, nixopsaws.backends.ec2.EC2State) } def ensure_state_up(self): config = self.get_defn() self._state["region"] = config["region"] if self._state.get('attachmentId', None): if self.state != self.UP: self.wait_for_eni_attachment(self._state['networkInterfaceId']) def wait_for_eni_attachment(self, eni_id): while True: response = self.get_client().describe_network_interface_attribute( Attribute='attachment', NetworkInterfaceId=eni_id) if response.get('Attachment', None): if response['Attachment']['Status'] == 'attached': break elif response['Attachment']['Status'] != "attaching": raise Exception( "eni attachment {0} in an unexpected state {1}".format( eni_id, response['Attachment']['Status'])) self.log_continue(".") time.sleep(1) else: raise Exception( "eni {} doesn't have any attachment {}".format(eni_id)) self.log_end(" done") with self.depl._db: self.state = self.UP def realize_create_eni_attachment(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception( "network interface attachment {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one". format(self._state['attachmentId'])) self.warn( "network interface attachment definition changed, recreating ..." ) self._destroy() self._state['region'] = config['region'] vm_id = config['instanceId'] if vm_id.startswith("res-"): res = self.depl.get_typed_resource(vm_id[4:].split(".")[0], "ec2") vm_id = res.vm_id eni_id = config['networkInterfaceId'] if eni_id.startswith("res-"): res = self.depl.get_typed_resource(eni_id[4:].split(".")[0], "vpc-network-interface") eni_id = res._state['networkInterfaceId'] self.log("attaching network interface {0} to instance {1}".format( eni_id, vm_id)) eni_attachment = self.get_client().attach_network_interface( DeviceIndex=config['deviceIndex'], InstanceId=vm_id, NetworkInterfaceId=eni_id) with self.depl._db: self.state = self.STARTING self._state['attachmentId'] = eni_attachment['AttachmentId'] self._state['instanceId'] = vm_id self._state['deviceIndex'] = config['deviceIndex'] self._state['networkInterfaceId'] = eni_id self.wait_for_eni_attachment(eni_id) def wait_for_eni_detachment(self): self.log( "waiting for eni attachment {0} to be detached from {1}".format( self._state['attachmentId'], self._state["instanceId"])) while True: response = self.get_client().describe_network_interface_attribute( Attribute='attachment', NetworkInterfaceId=self._state['networkInterfaceId']) if response.get('Attachment', None): if response['Attachment']['Status'] == 'detached': break elif response['Attachment']['Status'] != "detaching": raise Exception( "eni attachment {0} in an unexpected state {1}".format( eni_id, response['Attachment']['Status'])) self.log_continue(".") time.sleep(1) else: break self.log_end(" done") def _destroy(self): if self.state == self.UP: self.log("detaching vpc network interface attachment {}".format( self._state['attachmentId'])) try: self.get_client().detach_network_interface( AttachmentId=self._state['attachmentId'], Force=True) with self.depl._db: self.state = self.STOPPING except botocore.exceptions.ClientError as e: if e.response['Error'][ 'Code'] == "InvalidAttachmentID.NotFound": self.warn( "network interface attachment {} was already detached". format(self._state['attachmentId'])) else: raise e if self.state == self.STOPPING: self.wait_for_eni_detachment() with self.depl._db: self.state = self.MISSING self._state['region'] = None self._state['attachmentId'] = None self._state['instanceId'] = None self._state['deviceIndex'] = None self._state['networkInterfaceId'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCNetworkInterfaceState(nixops.resources.DiffEngineResourceState, EC2CommonState): """State of a VPC network interface""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['networkInterfaceId'] @classmethod def get_type(cls): return "vpc-network-interface" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.region = self._state.get('region', None) self.handle_create_eni = Handler(['region', 'subnetId', 'primaryPrivateIpAddress', 'privateIpAddresses', 'secondaryPrivateIpAddressCount'], handle=self.realize_create_eni) self.handle_modify_eni_attrs = Handler(['description', 'securityGroups', 'sourceDestCheck'], handle=self.realize_modify_eni_attrs, after=[self.handle_create_eni]) self.handle_tag_update = Handler(['tags'], after=[self.handle_create_eni], handle=self.realize_update_tag) def show_type(self): s = super(VPCNetworkInterfaceState, self).show_type() if self.region: s = "{0} [{1}]".format(s, self.region) return s @property def resource_id(self): return self._state.get('networkInterfaceId', None) def prefix_definition(self, attr): return {('resources', 'vpcNetworkInterfaces'): attr} def get_physical_spec(self): return { 'networkInterfaceId': self._state.get('networkInterfaceId', None) } def get_definition_prefix(self): return "resources.vpcNetworkInterfaces." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc_subnet.VPCSubnetState)} def realize_create_eni(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("network interface {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self._state['networkInterfaceId'])) self.warn("network interface definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] eni_input = self.network_interface_input(config) self.log("creating vpc network interface under {}".format(eni_input['SubnetId'])) response = self.get_client().create_network_interface(**eni_input) eni = response['NetworkInterface'] def split_ips(eni_ips): seconday = [] for ip in eni_ips: if ip['Primary']: primary = ip['PrivateIpAddress'] else: seconday.append(ip['PrivateIpAddress']) return primary, seconday primary, secondary = split_ips(eni['PrivateIpAddresses']) with self.depl._db: self.state = self.UP self._state['subnetId'] = eni_input['SubnetId'] self._state['networkInterfaceId'] = eni['NetworkInterfaceId'] self._state['primaryPrivateIpAddress'] = primary self._state['privateIpAddresses'] = secondary self._state['secondaryPrivateIpAddressCount'] = config['secondaryPrivateIpAddressCount'] def network_interface_input(self, config): subnet_id = config['subnetId'] if subnet_id.startswith("res-"): res = self.depl.get_typed_resource(subnet_id[4:].split(".")[0], "vpc-subnet") subnet_id = res._state['subnetId'] groups = [] for grp in config['securityGroups']: if grp.startswith("res-"): res = self.depl.get_typed_resource(grp[4:].split(".")[0], "ec2-security-group") assert res.vpc_id groups.append(res.security_group_id) else: groups.append(grp) primary_ip = config['primaryPrivateIpAddress'] secondary_private_ips = config['privateIpAddresses'] secondary_ip_count = config['secondaryPrivateIpAddressCount'] if (primary_ip and len(secondary_private_ips) > 0) and secondary_ip_count: raise Exception("you can't set privateIpAddresses/primaryPrivateIpAddress options together" " with secondaryPrivateIpAddressCount") ips = [] if primary_ip: ips.append({'Primary':True, 'PrivateIpAddress':primary_ip}) if len(secondary_private_ips) > 0: for ip in secondary_private_ips: ips.append({'Primary':False, 'PrivateIpAddress':ip}) cfg = dict() cfg['Description'] = config['description'] cfg['Groups'] = groups cfg['SubnetId'] = subnet_id if not secondary_ip_count: cfg['PrivateIpAddresses'] = ips else: cfg['secondaryPrivateIpAddressCount'] = secondary_ip_count return cfg def realize_modify_eni_attrs(self, allow_recreate): config = self.get_defn() self.log("applying network interface attribute changes") self.get_client().modify_network_interface_attribute(NetworkInterfaceId=self._state['networkInterfaceId'], Description={'Value':config['description']}) groups = [] for grp in config['securityGroups']: if grp.startswith("res-"): res = self.depl.get_typed_resource(grp[4:].split(".")[0], "ec2-security-group") assert res.vpc_id groups.append(res.security_group_id) else: groups.append(grp) if len(groups) >= 1: self.get_client().modify_network_interface_attribute( NetworkInterfaceId=self._state['networkInterfaceId'], Groups=groups) self.get_client().modify_network_interface_attribute(NetworkInterfaceId=self._state['networkInterfaceId'], SourceDestCheck={ 'Value':config['sourceDestCheck'] }) with self.depl._db: self._state['description'] = config['description'] self._state['securityGroups'] = groups self._state['sourceDestCheck'] = config['sourceDestCheck'] def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['networkInterfaceId']], Tags=[{"Key": k, "Value": tags[k]} for k in tags]) def _destroy(self): if self.state != self.UP: return self.log("deleting vpc network interface {}".format(self._state['networkInterfaceId'])) try: self.get_client().delete_network_interface(NetworkInterfaceId=self._state['networkInterfaceId']) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidNetworkInterfaceID.NotFound": self.warn("network interface {} was already deleted".format(self._state['networkInterfaceId'])) else: raise e with self.depl._db: self.state = self.MISSING self._state['networkInterfaceId'] = None self._state['primaryPrivateIpAddress'] = None self._state['privateIpAddresses'] = None self._state['secondaryPrivateIpAddressCount'] = None def destroy(self, wipe=False): self._destroy() return True
class VPCNetworkAclstate(nixops.resources.DiffEngineResourceState, EC2CommonState): """state of a vpc Network ACL.""" state = nixops.util.attr_property("state", nixops.resources.DiffEngineResourceState.MISSING, int) access_key_id = nixops.util.attr_property("accessKeyId", None) _reserved_keys = EC2CommonState.COMMON_EC2_RESERVED + ['networkAclId'] @classmethod def get_type(cls): return "vpc-network-acl" def __init__(self, depl, name, id): nixops.resources.DiffEngineResourceState.__init__(self, depl, name, id) self._state = StateDict(depl, id) self.network_acl_id = self._state.get('networkAclId', None) self.handle_create_network_acl = Handler(['region', 'vpcId'], handle=self.realize_create_network_acl) self.handle_entries = Handler(['entries'], after=[self.handle_create_network_acl] , handle=self.realize_entries_change) self.handle_subnet_association = Handler(['subnetIds'], after=[self.handle_create_network_acl] , handle=self.realize_subnets_change) self.handle_tag_update = Handler(['tags'], after=[self.handle_create_network_acl], handle=self.realize_update_tag) def show_type(self): s = super(VPCNetworkAclstate, self).show_type() if self._state.get('region', None): s = "{0} [{1}]".format(s, self._state['region']) return s @property def resource_id(self): return self.network_acl_id def prefix_definition(self, attr): return {('resources', 'vpcNetworkAcls'): attr} def get_definition_prefix(self): return "resources.vpcNetworkAcls." def create_after(self, resources, defn): return {r for r in resources if isinstance(r, nixops.resources.vpc.VPCState) or isinstance(r, nixops.resources.vpc_subnet.VPCSubnetState)} def realize_create_network_acl(self, allow_recreate): config = self.get_defn() if self.state == self.UP: if not allow_recreate: raise Exception("network ACL {} definition changed and it needs to be recreated" " use --allow-recreate if you want to create a new one".format(self.network_acl_id)) self.warn("network ACL definition changed, recreating ...") self._destroy() self._state['region'] = config['region'] vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] self.log("creating network ACL in vpc {}".format(vpc_id)) response = self.get_client().create_network_acl(VpcId=vpc_id) self.network_acl_id = response['NetworkAcl']['NetworkAclId'] with self.depl._db: self.state = self.UP self._state['vpcId'] = vpc_id self._state['networkAclId'] = self.network_acl_id def realize_entries_change(self, allow_recreate): config = self.get_defn() old_entries = self._state.get('entries', []) new_entries = config['entries'] to_remove = [e for e in old_entries if e not in new_entries] to_create = [e for e in new_entries if e not in old_entries] for entry in to_remove: try: self.get_client().delete_network_acl_entry(NetworkAclId=self.network_acl_id, RuleNumber=entry['ruleNumber'], Egress=entry['egress']) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "InvalidNetworkAclEntry.NotFound": self.warn("rule {0} was already deleted from network ACL {1}".format(entry['ruleNumber'], self.network_acl_id)) else: raise e for entry in to_create: rule = self.process_rule_entry(entry) self.get_client().create_network_acl_entry(**rule) with self.depl._db: self._state['entries'] = config['entries'] def realize_subnets_change(self, allow_recreate): config = self.get_defn() old_subnets = self._state.get('subnetIds', []) new_subnets = [] for s in config['subnetIds']: if s.startswith("res-"): res = self.depl.get_typed_resource(s[4:].split(".")[0], "vpc-subnet") new_subnets.append(res._state['subnetId']) else: new_subnets.append(s) vpc_id = config['vpcId'] if vpc_id.startswith("res-"): res = self.depl.get_typed_resource(vpc_id[4:].split(".")[0], "vpc") vpc_id = res._state['vpcId'] subnets_to_remove = [s for s in old_subnets if s not in new_subnets] subnets_to_add = [s for s in new_subnets if s not in old_subnets] default_network_acl = self.get_default_network_acl(vpc_id) for subnet in subnets_to_remove: association_id = self.get_network_acl_association(subnet) self.log("associating subnet {0} to default network acl {1}".format(subnet, default_network_acl)) self.get_client().replace_network_acl_association(AssociationId=association_id, NetworkAclId=default_network_acl) for subnet in subnets_to_add: association_id = self.get_network_acl_association(subnet) self.log("associating subnet {0} to network acl {1}".format(subnet, self.network_acl_id)) self.get_client().replace_network_acl_association(AssociationId=association_id, NetworkAclId=self.network_acl_id) with self.depl._db: self._state['subnetIds'] = new_subnets def get_default_network_acl(self, vpc_id): response = self.get_client().describe_network_acls(Filters=[{ "Name": "default", "Values": [ "true" ] }, { "Name": "vpc-id", "Values": [ vpc_id ]}]) return response['NetworkAcls'][0]['NetworkAclId'] def get_network_acl_association(self, subnet_id): response = self.get_client().describe_network_acls(Filters=[{"Name": "association.subnet-id", "Values":[ subnet_id ]}]) for association in response['NetworkAcls'][0]['Associations']: if association['SubnetId'] == subnet_id: return association['NetworkAclAssociationId'] def process_rule_entry(self, entry): rule = dict() rule['NetworkAclId'] = self.network_acl_id rule['Protocol'] = entry['protocol'] rule['RuleNumber'] = entry['ruleNumber'] rule['RuleAction'] = entry['ruleAction'] rule['Egress'] = entry['egress'] if entry['cidrBlock'] is not None: rule['CidrBlock'] = entry['cidrBlock'] if entry['ipv6CidrBlock'] is not None: rule['Ipv6CidrBlock'] = entry['ipv6CidrBlock'] if entry['icmpCode'] and entry['icmpType']: rule['IcmpTypeCode'] = {"Type": entry['icmpType'], "Code": entry['icmpCode']} if entry['fromPort'] and entry['toPort']: rule['PortRange'] = { "From": entry['fromPort'], "To": entry['toPort'] } return rule def _destroy(self): if self.state != self.UP: return try: subnets = self._state.get('subnetIds', []) default_network_acl = self.get_default_network_acl(self._state['vpcId']) for subnet in subnets: association_id = self.get_network_acl_association(subnet) self.log("associating subnet {0} to default network acl {1}".format(subnet, default_network_acl)) self.get_client().replace_network_acl_association(AssociationId=association_id, NetworkAclId=default_network_acl) self.log("deleting network acl {}".format(self.network_acl_id)) self.get_client().delete_network_acl(NetworkAclId=self.network_acl_id) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'InvalidNetworkAclID.NotFound': self.warn("network ACL {} was already deleted".format(self.network_acl_id)) else: raise e with self.depl._db: self.state = self.MISSING self._state['networkAclId'] = None self._state['region'] = None self._state['vpcId'] = None self._state['entries'] = None def realize_update_tag(self, allow_recreate): config = self.get_defn() tags = config['tags'] tags.update(self.get_common_tags()) self.get_client().create_tags(Resources=[self._state['networkAclId']], Tags=[{"Key": k, "Value": tags[k]} for k in tags]) def destroy(self, wipe=False): self._destroy() return True