Exemple #1
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
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
Exemple #10
0
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
Exemple #11
0
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
Exemple #12
0
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
Exemple #15
0
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
Exemple #17
0
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
Exemple #19
0
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
Exemple #21
0
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
Exemple #23
0
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