pa_api.xmlapi.types.config.address

  1# Given a list of subnets,
  2# Find all NAT rules related to an address in the subnet
  3
  4import string
  5from ipaddress import IPv4Network, IPv6Network, ip_network
  6from typing import TYPE_CHECKING, Optional, Union
  7
  8from pydantic import AliasChoices, AliasPath, Field
  9from pydantic.functional_validators import field_validator, model_validator
 10from typing_extensions import Self
 11
 12from pa_api.xmlapi.types.utils import List, ObjectBaseModel, String, XMLBaseModel
 13
 14IPNetwork = Union[IPv4Network, IPv6Network]
 15
 16
 17def get_ip_network(ip_netmask):
 18    try:
 19        if ip_netmask:
 20            return ip_network(ip_netmask, strict=False)
 21    except Exception:
 22        return None
 23
 24
 25# https://docs.pydantic.dev/latest/concepts/alias/#aliaspath-and-aliaschoices
 26class Address(XMLBaseModel):
 27    name: str = Field(validation_alias="@name")
 28    type: Optional[str] = None
 29    prefix: Optional[str] = None
 30    ip_netmask: Optional[str] = Field(
 31        alias="ip-netmask",
 32        validation_alias=AliasChoices(
 33            AliasPath("ip-netmask", "#text"),
 34            "ip-netmask",
 35        ),
 36        default=None,
 37    )
 38    ip_network: Optional[IPNetwork] = None
 39    ip_range: Optional[str] = Field(alias="ip-range", default=None)
 40    fqdn: Optional[String] = None
 41    tags: List[String] = Field(
 42        validation_alias=AliasPath("tag", "member"),
 43        default_factory=list,
 44    )
 45
 46    @field_validator("tags", mode="before")
 47    @classmethod
 48    def validate_tags(cls, v) -> List[str]:
 49        if not v:
 50            return []
 51        if not isinstance(v, list):
 52            return [v]
 53        return v
 54
 55    @model_validator(mode="after")
 56    def validate_ip_network(self) -> Self:
 57        if self.ip_network is None:
 58            self.ip_network = get_ip_network(self.ip_netmask)
 59        if not isinstance(self.ip_network, (IPv4Network, IPv6Network)):
 60            self.ip_network = None
 61        return self
 62
 63    @model_validator(mode="after")
 64    def validate_type(self) -> Self:
 65        address_type = None
 66        if self.prefix:
 67            address_type = "prefix"
 68        elif self.ip_netmask:
 69            address_type = "ip-netmask"
 70        elif self.ip_range:
 71            address_type = "ip-range"
 72        elif self.fqdn:
 73            address_type = "fqdn"
 74        self.type = address_type
 75        return self
 76
 77
 78# ======================================================
 79# Filtering tools
 80
 81
 82def take_until_marker(it, marker):
 83    for c in it:
 84        if c == marker:
 85            return
 86        yield c
 87
 88
 89def take_word(it):
 90    for c in it:
 91        if c in string.whitespace:
 92            return
 93        yield c
 94
 95
 96WORD_CHARSET = string.ascii_letters + string.digits + "_-"
 97AND = 1
 98OR = 2
 99
100
101def _parse_filter(it):
102    node = []
103    for c in it:
104        if c in WORD_CHARSET:
105            value = c
106            for c in it:
107                if c not in WORD_CHARSET:
108                    if value == "or":
109                        value = OR
110                    elif value == "and":
111                        value = AND
112                    node.append(value)
113                    break
114                value += c
115            else:  # We didn't break => We reached the end of the string
116                node.append(value)
117                return node
118        if c == "(":
119            subnode = _parse_filter(it)
120            node.append(subnode)
121        if c == ")":
122            return node
123        if c in ("'", '"'):
124            value = "".join(take_until_marker(it, c))
125            node.append(value)
126    return node
127
128
129def _dump_raw_node(node):
130    for e in node:
131        if e == AND:
132            yield "and"
133        elif e == OR:
134            yield "or"
135        elif isinstance(e, list):
136            yield f"({' '.join(_dump_raw_node(e))})"
137        else:
138            yield repr(e)
139
140
141def dump_raw_node(node):
142    return " ".join(_dump_raw_node(node))
143
144
145def parse_filter(text: str):
146    it = iter(text)
147    return _parse_filter(it)
148
149
150# ======================================================
151
152if TYPE_CHECKING:
153    from pa_api.xmlapi.clients import Client
154
155
156class DynamicFilter(XMLBaseModel):
157    filter: String
158
159    @property
160    def sanitized_filter(self) -> str:
161        raw_node = parse_filter(self.filter)
162        filter = dump_raw_node(raw_node)
163        return filter
164
165
166class AddressGroup(ObjectBaseModel):
167    __resource_xpath__ = "/config/devices/entry/device-group/entry/address-group/entry"
168
169    name: str = Field(validation_alias="@name")
170    description: String = ""
171    disable_override: Optional[bool] = Field(alias="disable-override", default=None)
172
173    @property
174    def xpath(self):
175        return f"{self.__resource_xpath__}[@name='{self.name}']"
176
177    @property
178    def type(self):
179        if self.static_members and self.dynamic_members is not None:
180            raise Exception("AddressGroup is both dynamic and static")
181        if self.dynamic_members is not None:
182            return "dynamic"
183        return "static"
184
185    static_members: List[String] = Field(alias="static", default_factory=list)
186    dynamic_members: Optional[DynamicFilter] = Field(
187        validation_alias="dynamic", default=None
188    )
189
190    tags: List[String] = Field(
191        validation_alias=AliasPath("tag", "member"), default_factory=list
192    )
193
194    def remove_member(self, client: "Client", member: str):
195        """
196        Remove the member from destination.
197
198        NOTE: Rulebase information is required for panorama
199        """
200        # panorama_rule_xpath = f"/config/devices/entry/vsys/entry/rulebase/security/rules/entry[@uuid='{self.uuid}']"
201        member_xpath = f"{self.xpath}/static/member[text()='{member}']"
202        return client.configuration.delete(member_xpath)
203
204
205# def find_addresses(tree):
206#     # addresses_xml = tree.xpath(".//address/entry")
207#     addresses_xml = tree.xpath("./devices/entry/device-group//address/entry")
208#     address_objects = [Address.from_xml(n) for n in addresses_xml]
209
210#     addresses = []
211#     subnets = []
212#     for a in address_objects:
213#         network = a.ip_network
214#         # We do not consider ip ranges for now
215#         if not network:
216#             continue
217#         if network.prefixlen == network.max_prefixlen:
218#             addresses.append(a)
219#         else:
220#             subnets.append(a)
221#     return addresses, subnets
IPNetwork = typing.Union[ipaddress.IPv4Network, ipaddress.IPv6Network]
def get_ip_network(ip_netmask):
18def get_ip_network(ip_netmask):
19    try:
20        if ip_netmask:
21            return ip_network(ip_netmask, strict=False)
22    except Exception:
23        return None
class Address(pa_api.xmlapi.types.utils.XMLBaseModel):
27class Address(XMLBaseModel):
28    name: str = Field(validation_alias="@name")
29    type: Optional[str] = None
30    prefix: Optional[str] = None
31    ip_netmask: Optional[str] = Field(
32        alias="ip-netmask",
33        validation_alias=AliasChoices(
34            AliasPath("ip-netmask", "#text"),
35            "ip-netmask",
36        ),
37        default=None,
38    )
39    ip_network: Optional[IPNetwork] = None
40    ip_range: Optional[str] = Field(alias="ip-range", default=None)
41    fqdn: Optional[String] = None
42    tags: List[String] = Field(
43        validation_alias=AliasPath("tag", "member"),
44        default_factory=list,
45    )
46
47    @field_validator("tags", mode="before")
48    @classmethod
49    def validate_tags(cls, v) -> List[str]:
50        if not v:
51            return []
52        if not isinstance(v, list):
53            return [v]
54        return v
55
56    @model_validator(mode="after")
57    def validate_ip_network(self) -> Self:
58        if self.ip_network is None:
59            self.ip_network = get_ip_network(self.ip_netmask)
60        if not isinstance(self.ip_network, (IPv4Network, IPv6Network)):
61            self.ip_network = None
62        return self
63
64    @model_validator(mode="after")
65    def validate_type(self) -> Self:
66        address_type = None
67        if self.prefix:
68            address_type = "prefix"
69        elif self.ip_netmask:
70            address_type = "ip-netmask"
71        elif self.ip_range:
72            address_type = "ip-range"
73        elif self.fqdn:
74            address_type = "fqdn"
75        self.type = address_type
76        return self

!!! abstract "Usage Documentation" Models

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
name: str
type: Optional[str]
prefix: Optional[str]
ip_netmask: Optional[str]
ip_network: Union[ipaddress.IPv4Network, ipaddress.IPv6Network, NoneType]
ip_range: Optional[str]
fqdn: Optional[Annotated[str, BeforeValidator(func=<function ensure_str at 0x7f4bd793fac0>, json_schema_input_type=PydanticUndefined)]]
tags: Annotated[List[Annotated[str, BeforeValidator(func=<function ensure_str at 0x7f4bd793fac0>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7f4bd793f9a0>, json_schema_input_type=PydanticUndefined)]
@field_validator('tags', mode='before')
@classmethod
def validate_tags( cls, v) -> Annotated[List[str], BeforeValidator(func=<function ensure_list at 0x7f4bd793f9a0>, json_schema_input_type=PydanticUndefined)]:
47    @field_validator("tags", mode="before")
48    @classmethod
49    def validate_tags(cls, v) -> List[str]:
50        if not v:
51            return []
52        if not isinstance(v, list):
53            return [v]
54        return v
@model_validator(mode='after')
def validate_ip_network(self) -> typing_extensions.Self:
56    @model_validator(mode="after")
57    def validate_ip_network(self) -> Self:
58        if self.ip_network is None:
59            self.ip_network = get_ip_network(self.ip_netmask)
60        if not isinstance(self.ip_network, (IPv4Network, IPv6Network)):
61            self.ip_network = None
62        return self
@model_validator(mode='after')
def validate_type(self) -> typing_extensions.Self:
64    @model_validator(mode="after")
65    def validate_type(self) -> Self:
66        address_type = None
67        if self.prefix:
68            address_type = "prefix"
69        elif self.ip_netmask:
70            address_type = "ip-netmask"
71        elif self.ip_range:
72            address_type = "ip-range"
73        elif self.fqdn:
74            address_type = "fqdn"
75        self.type = address_type
76        return self
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

def take_until_marker(it, marker):
83def take_until_marker(it, marker):
84    for c in it:
85        if c == marker:
86            return
87        yield c
def take_word(it):
90def take_word(it):
91    for c in it:
92        if c in string.whitespace:
93            return
94        yield c
WORD_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'
AND = 1
OR = 2
def dump_raw_node(node):
142def dump_raw_node(node):
143    return " ".join(_dump_raw_node(node))
def parse_filter(text: str):
146def parse_filter(text: str):
147    it = iter(text)
148    return _parse_filter(it)
class DynamicFilter(pa_api.xmlapi.types.utils.XMLBaseModel):
157class DynamicFilter(XMLBaseModel):
158    filter: String
159
160    @property
161    def sanitized_filter(self) -> str:
162        raw_node = parse_filter(self.filter)
163        filter = dump_raw_node(raw_node)
164        return filter

!!! abstract "Usage Documentation" Models

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
filter: typing.Annotated[str, BeforeValidator(func=<function ensure_str at 0x7f4bd793fac0>, json_schema_input_type=PydanticUndefined)]
sanitized_filter: str
160    @property
161    def sanitized_filter(self) -> str:
162        raw_node = parse_filter(self.filter)
163        filter = dump_raw_node(raw_node)
164        return filter
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class AddressGroup(pa_api.xmlapi.types.utils.ObjectBaseModel):
167class AddressGroup(ObjectBaseModel):
168    __resource_xpath__ = "/config/devices/entry/device-group/entry/address-group/entry"
169
170    name: str = Field(validation_alias="@name")
171    description: String = ""
172    disable_override: Optional[bool] = Field(alias="disable-override", default=None)
173
174    @property
175    def xpath(self):
176        return f"{self.__resource_xpath__}[@name='{self.name}']"
177
178    @property
179    def type(self):
180        if self.static_members and self.dynamic_members is not None:
181            raise Exception("AddressGroup is both dynamic and static")
182        if self.dynamic_members is not None:
183            return "dynamic"
184        return "static"
185
186    static_members: List[String] = Field(alias="static", default_factory=list)
187    dynamic_members: Optional[DynamicFilter] = Field(
188        validation_alias="dynamic", default=None
189    )
190
191    tags: List[String] = Field(
192        validation_alias=AliasPath("tag", "member"), default_factory=list
193    )
194
195    def remove_member(self, client: "Client", member: str):
196        """
197        Remove the member from destination.
198
199        NOTE: Rulebase information is required for panorama
200        """
201        # panorama_rule_xpath = f"/config/devices/entry/vsys/entry/rulebase/security/rules/entry[@uuid='{self.uuid}']"
202        member_xpath = f"{self.xpath}/static/member[text()='{member}']"
203        return client.configuration.delete(member_xpath)

!!! abstract "Usage Documentation" Models

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
name: str
description: typing.Annotated[str, BeforeValidator(func=<function ensure_str at 0x7f4bd793fac0>, json_schema_input_type=PydanticUndefined)]
disable_override: Optional[bool]
xpath
174    @property
175    def xpath(self):
176        return f"{self.__resource_xpath__}[@name='{self.name}']"
type
178    @property
179    def type(self):
180        if self.static_members and self.dynamic_members is not None:
181            raise Exception("AddressGroup is both dynamic and static")
182        if self.dynamic_members is not None:
183            return "dynamic"
184        return "static"
static_members: Annotated[List[Annotated[str, BeforeValidator(func=<function ensure_str at 0x7f4bd793fac0>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7f4bd793f9a0>, json_schema_input_type=PydanticUndefined)]
dynamic_members: Optional[DynamicFilter]
tags: Annotated[List[Annotated[str, BeforeValidator(func=<function ensure_str at 0x7f4bd793fac0>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7f4bd793f9a0>, json_schema_input_type=PydanticUndefined)]
def remove_member(self, client: pa_api.xmlapi.Client, member: str):
195    def remove_member(self, client: "Client", member: str):
196        """
197        Remove the member from destination.
198
199        NOTE: Rulebase information is required for panorama
200        """
201        # panorama_rule_xpath = f"/config/devices/entry/vsys/entry/rulebase/security/rules/entry[@uuid='{self.uuid}']"
202        member_xpath = f"{self.xpath}/static/member[text()='{member}']"
203        return client.configuration.delete(member_xpath)

Remove the member from destination.

NOTE: Rulebase information is required for panorama

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].