diff --git a/pyproject.toml b/pyproject.toml index 2246bf87d..0c5752e4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "vmngclient" -version = "0.15.1" +version = "0.16.0" description = "vManage SDK for Python" authors = ["kagorski "] readme = "README.md" diff --git a/vmngclient/api/template_api.py b/vmngclient/api/template_api.py index cfda26591..6221ccf4e 100644 --- a/vmngclient/api/template_api.py +++ b/vmngclient/api/template_api.py @@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Any, Optional, Type, overload from ciscoconfparse import CiscoConfParse # type: ignore -from requests.exceptions import HTTPError from vmngclient.api.task_status_api import Task from vmngclient.api.templates.cli_template import CLITemplate @@ -211,10 +210,6 @@ def _attach_cli(self, name: str, device: Device, is_edited: bool = False) -> boo except TemplateNotFoundError: logger.error(f"Error, Template with name {name} not found on {device}.") return False - except HTTPError as error: - error_details = json.loads(error.response.text) - logger.error(f"Error in config: {error_details['error']['details']}.") - return False payload = { "deviceTemplateList": [ { @@ -564,7 +559,7 @@ def generate_feature_template_payload( # "name" for i, field in enumerate(fr_template_fields): value = None - + priority_order = None # TODO How to discover Device specific variable if field.key in template.device_specific_variables: value = template.device_specific_variables[field.key] @@ -574,6 +569,7 @@ def generate_feature_template_payload( field.key == field_value.alias or field.key == field_value.field_info.extra.get("vmanage_key") # type: ignore ): + priority_order = field_value.field_info.extra.get("priority_order") # type: ignore value = getattr(template, field_name) break if value is None: @@ -597,7 +593,8 @@ def merge(a, b, path=None): a[key] = b[key] return a - payload.definition = merge(payload.definition, field.payload_scheme(value)) + # print(field.payload_scheme(value)) + payload.definition = merge(payload.definition, field.payload_scheme(value, priority_order=priority_order)) if debug: with open(f"payload_{template.type}.json", "w") as f: @@ -674,10 +671,6 @@ def edit_before_push(self, name: str, device: Device) -> bool: except TemplateNotFoundError: logger.error(f"Error, Template with name {name} not found on {device}.") return False - except HTTPError as error: - error_details = json.loads(error.response.text) - logger.error(f"Error in config: {error_details['error']['details']}.") - return False payload = { "templateId": template_id, "deviceIds": [device.uuid], diff --git a/vmngclient/api/templates/cli_template.py b/vmngclient/api/templates/cli_template.py index 74235c11c..bf3bfd9c5 100644 --- a/vmngclient/api/templates/cli_template.py +++ b/vmngclient/api/templates/cli_template.py @@ -108,9 +108,12 @@ def update(self, session: vManageSession, id: str, config: CiscoConfParse) -> bo try: session.put(url=endpoint, json=payload) except HTTPError as error: - response = json.loads(error.response.text)["error"] - logger.error(f'Response message: {response["message"]}') - logger.error(f'Response details: {response["details"]}') + if error.response: + response = json.loads(error.response.text)["error"] + logger.error(f'Response message: {response["message"]}') + logger.error(f'Response details: {response["details"]}') + else: + logger.error("Response is None.") return False logger.info(f"Template with name: {self.template_name} - updated.") return True diff --git a/vmngclient/api/templates/feature_template_field.py b/vmngclient/api/templates/feature_template_field.py index 72943c364..b951e041f 100644 --- a/vmngclient/api/templates/feature_template_field.py +++ b/vmngclient/api/templates/feature_template_field.py @@ -86,7 +86,7 @@ def data_path(self, output): return output # value must be JSON serializable, return JSON serializable dict - def payload_scheme(self, value: Any = None, help=None, current_path=None) -> dict: + def payload_scheme(self, value: Any = None, help=None, current_path=None, priority_order=None) -> dict: output: dict = {} rel_output: dict = {} rel_output.update(get_path_dict([self.dataPath])) @@ -110,7 +110,7 @@ def nest_value_in_output(value: Any) -> dict: return nest_value_in_output(vip_variable.dict(by_alias=True, exclude_none=True)) else: - if value: + if value is not None: output["vipType"] = FeatureTemplateOptionType.CONSTANT.value if self.children: children_output = [] @@ -129,14 +129,21 @@ def nest_value_in_output(value: Any) -> dict: ) ) obj_value = getattr(obj, model_field.name) + po = model_field.field_info.extra.get("priority_order") child_payload.update( - child.payload_scheme(obj_value, help=output, current_path=self.dataPath + [self.key]) + child.payload_scheme( + obj_value, help=output, current_path=self.dataPath + [self.key], priority_order=po + ) ) + if priority_order: + child_payload.update({"priority-order": priority_order}) children_output.append(child_payload) output["vipValue"] = children_output else: output["vipValue"] = value else: + if value is None: + return {} if "default" in self.dataType: return {} # output["vipValue"] = self.dataType["default"] if value is None else value diff --git a/vmngclient/api/templates/models/cisco_aaa_model.py b/vmngclient/api/templates/models/cisco_aaa_model.py index d22acb999..6e90e230f 100644 --- a/vmngclient/api/templates/models/cisco_aaa_model.py +++ b/vmngclient/api/templates/models/cisco_aaa_model.py @@ -12,7 +12,7 @@ class User(BaseModel): password: str secret: str privilege: Optional[str] - pubkey_chain: List[str] = Field(default=[], vmanage_key="pubkey-chain") + pubkey_chain: Optional[List[str]] = Field(default=None, vmanage_key="pubkey-chain") class RadiusServer(BaseModel): @@ -77,10 +77,10 @@ class Config: user: List[User] = [] authentication_group: bool = Field(vmanage_key="authentication_group", default=False) accounting_group: bool = True - radius: List[RadiusGroup] = [] + radius: Optional[List[RadiusGroup]] = None domain_stripping: Optional[DomainStripping] = Field(vmanage_key="domain-stripping", default=None) port: int = 1700 - tacacs: List[TacacsGroup] = [] + tacacs: Optional[List[TacacsGroup]] = None server_auth_order: str = Field(vmanage_key="server-auth-order", default="local") payload_path: ClassVar[Path] = Path(__file__).parent / "DEPRECATED" diff --git a/vmngclient/api/templates/models/cisco_vpn_model.py b/vmngclient/api/templates/models/cisco_vpn_model.py index 7bee94ccc..3f2ed6cf9 100644 --- a/vmngclient/api/templates/models/cisco_vpn_model.py +++ b/vmngclient/api/templates/models/cisco_vpn_model.py @@ -82,10 +82,10 @@ class NextHopWithTrack(BaseModel): class Routev4(BaseModel): prefix: str - next_hop: Optional[List[NextHop]] = Field(vmanage_key="next-hop") - next_hop_with_track: Optional[List[NextHopWithTrack]] = Field(vmanage_key="next-hop-with-track") + next_hop: Optional[List[NextHop]] = Field(vmanage_key="next-hop", priority_order=["address", "distance"]) + next_hop_with_track: Optional[List[NextHopWithTrack]] = Field(default=None, vmanage_key="next-hop-with-track") null0: Optional[bool] - distance: Optional[int] = 1 + distance: Optional[int] = None vpn: Optional[int] dhcp: Optional[bool] @@ -400,10 +400,12 @@ class Config: dns: Optional[List[Dns]] dns_ipv6: Optional[List[DnsIpv6]] = Field(vmanage_key="dns-ipv6") layer4: Optional[bool] = Field(data_path=["ecmp-hash-key"]) - host: Optional[List[Host]] + host: Optional[List[Host]] = Field(priority_order=["hostname", "ip"]) service: Optional[List[Service]] service_route: Optional[List[ServiceRoute]] = Field(data_path=["ip"], vmanage_key="service-route") - route_v4: Optional[List[Routev4]] = Field(data_path=["ip"], vmanage_key="route") + route_v4: Optional[List[Routev4]] = Field( + data_path=["ip"], vmanage_key="route", priority_order=["next-hop", "next-hop-with-track", "prefix"] + ) route_v6: Optional[List[Routev6]] = Field(data_path=["ipv6"], vmanage_key="route") gre_route: Optional[List[GreRoute]] = Field(data_path=["ip"], vmanage_key="gre-route") ipsec_route: Optional[List[IpsecRoute]] = Field(data_path=["ip"], vmanage_key="ipsec-route") @@ -420,3 +422,10 @@ class Config: payload_path: ClassVar[Path] = Path(__file__).parent / "DEPRECATED" type: ClassVar[str] = "cisco_vpn" + + def generate_vpn_id(self, session): + if self.vpn_id not in [0, 512]: + payload = {"resourcePoolDataType": "vpn", "tenantId": self.org_name, "tenantVpn": self.vpn_id} + url = "/dataservice/resourcepool/resource/vpn" + response = session.put(url=url, json=payload).json() + self.vpn_id = response["deviceVpn"] diff --git a/vmngclient/tests/templates/definitions/Basic_Cisco_VPN_Model.json b/vmngclient/tests/templates/definitions/Basic_Cisco_VPN_Model.json index d8cce1e31..957d38684 100644 --- a/vmngclient/tests/templates/definitions/Basic_Cisco_VPN_Model.json +++ b/vmngclient/tests/templates/definitions/Basic_Cisco_VPN_Model.json @@ -7,5 +7,11 @@ ], "factoryDefault": false, "templateMinVersion": "15.0.0", - "templateDefinition": {} + "templateDefinition": { + "vpn-id": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": 0 + } + } } diff --git a/vmngclient/tests/templates/definitions/basic/children_nested.json b/vmngclient/tests/templates/definitions/basic/children_nested.json index 40792c51a..0b0ba1b56 100644 --- a/vmngclient/tests/templates/definitions/basic/children_nested.json +++ b/vmngclient/tests/templates/definitions/basic/children_nested.json @@ -1,12 +1,14 @@ { "user": { "vipObjectType": "tree", - "vipPrimaryKey": [ - "name" - ], "vipType": "constant", "vipValue": [ { + "name": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": "user1" + }, "list": { "password": { "vipObjectType": "object", @@ -14,16 +16,8 @@ "vipValue": "pass" } }, - "name": { - "vipObjectType": "object", - "vipType": "constant", - "vipValue": "user1" - }, "pubkey-chain": { "vipObjectType": "tree", - "vipPrimaryKey": [ - "key-string" - ], "vipType": "constant", "vipValue": [ { @@ -42,10 +36,18 @@ } } } + ], + "vipPrimaryKey": [ + "key-string" ] } }, { + "name": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": "user2" + }, "list": { "password": { "vipObjectType": "object", @@ -53,21 +55,18 @@ "vipValue": "pass" } }, - "name": { - "vipObjectType": "object", - "vipType": "constant", - "vipValue": "user2" - }, "pubkey-chain": { "vipObjectType": "tree", + "vipType": "constant", + "vipValue": [], "vipPrimaryKey": [ "key-string" - ], - "vipType": "ignore", - "vipValue": [ ] } } + ], + "vipPrimaryKey": [ + "name" ] } -} +} \ No newline at end of file diff --git a/vmngclient/tests/templates/definitions/basic/children_nested_datapath.json b/vmngclient/tests/templates/definitions/basic/children_nested_datapath.json index 503626e8d..0b0ba1b56 100644 --- a/vmngclient/tests/templates/definitions/basic/children_nested_datapath.json +++ b/vmngclient/tests/templates/definitions/basic/children_nested_datapath.json @@ -57,8 +57,8 @@ }, "pubkey-chain": { "vipObjectType": "tree", + "vipType": "constant", "vipValue": [], - "vipType": "ignore", "vipPrimaryKey": [ "key-string" ] diff --git a/vmngclient/tests/templates/definitions/complex_aaa.json b/vmngclient/tests/templates/definitions/complex_aaa.json index 5f8aa24f1..7aff03b45 100644 --- a/vmngclient/tests/templates/definitions/complex_aaa.json +++ b/vmngclient/tests/templates/definitions/complex_aaa.json @@ -35,14 +35,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "1" - }, - "pubkey-chain": { - "vipObjectType": "tree", - "vipValue": [], - "vipType": "ignore", - "vipPrimaryKey": [ - "key-string" - ] } }, { @@ -65,14 +57,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "15" - }, - "pubkey-chain": { - "vipObjectType": "tree", - "vipValue": [], - "vipType": "ignore", - "vipPrimaryKey": [ - "key-string" - ] } } ], @@ -139,11 +123,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "secret_key" - }, - "key-enum": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } } ], @@ -207,11 +186,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "secret_key2" - }, - "key-enum": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } } ], @@ -235,6 +209,11 @@ "vipType": "constant", "vipValue": "group1" }, + "vpn": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": 0 + }, "source-interface": { "vipObjectType": "object", "vipType": "constant", @@ -269,11 +248,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "secret_key" - }, - "key-enum": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } } ], diff --git a/vmngclient/tests/templates/definitions/complex_cisco_vpn.json b/vmngclient/tests/templates/definitions/complex_cisco_vpn.json index 8d649a6bf..1ce337bcc 100644 --- a/vmngclient/tests/templates/definitions/complex_cisco_vpn.json +++ b/vmngclient/tests/templates/definitions/complex_cisco_vpn.json @@ -8,6 +8,11 @@ "factoryDefault": false, "templateMinVersion": "15.0.0", "templateDefinition": { + "vpn-id": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": 0 + }, "name": { "vipObjectType": "object", "vipType": "constant", @@ -86,7 +91,11 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "test_hostname" - }, + }, + "priority-order": [ + "hostname", + "ip" + ], "ip": { "vipObjectType": "list", "vipType": "constant", @@ -236,7 +245,12 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "prefixv4" - }, + }, + "priority-order": [ + "next-hop", + "next-hop-with-track", + "prefix" + ], "next-hop": { "vipObjectType": "tree", "vipType": "constant", @@ -247,6 +261,10 @@ "vipType": "constant", "vipValue": "1.1.1.1" }, + "priority-order": [ + "address", + "distance" + ], "distance": { "vipObjectType": "object", "vipType": "constant", @@ -257,19 +275,6 @@ "vipPrimaryKey": [ "address" ] - }, - "next-hop-with-track": { - "vipObjectType": "tree", - "vipValue": [], - "vipType": "ignore", - "vipPrimaryKey": [ - "address" - ] - }, - "distance": { - "vipObjectType": "object", - "vipType": "constant", - "vipValue": 1 } } ], @@ -291,11 +296,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": 100 - }, - "interface": { - "vipObjectType": "list", - "vipValue": [], - "vipType": "ignore" } }, { @@ -357,11 +357,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": 100 - }, - "interface": { - "vipObjectType": "list", - "vipValue": [], - "vipType": "ignore" } } ], @@ -495,6 +490,11 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "prefix_entryv6" + }, + "aggregate-only": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": false } } ], @@ -571,6 +571,11 @@ "vipType": "constant", "vipValue": "10.10.10.10" }, + "overload": { + "vipObjectType": "node-only", + "vipType": "constant", + "vipValue": false + }, "leak_from_global": { "vipObjectType": "object", "vipType": "constant", @@ -580,6 +585,11 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "connected" + }, + "leak_to_global": { + "vipObjectType": "object", + "vipType": "constant", + "vipValue": false } } ], @@ -661,11 +671,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "outside" - }, - "tracker-id": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } } ], @@ -724,11 +729,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "outside" - }, - "tracker-id": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } } ], @@ -761,11 +761,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "outside" - }, - "tracker-id": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } }, { @@ -1068,4 +1063,4 @@ ] } } -} +} \ No newline at end of file diff --git a/vmngclient/tests/templates/definitions/iuo.json b/vmngclient/tests/templates/definitions/iuo.json index 46089f31a..b30dc66ee 100644 --- a/vmngclient/tests/templates/definitions/iuo.json +++ b/vmngclient/tests/templates/definitions/iuo.json @@ -37,14 +37,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "15" - }, - "pubkey-chain": { - "vipObjectType": "tree", - "vipValue": [], - "vipType": "ignore", - "vipPrimaryKey": [ - "key-string" - ] } }, { @@ -67,14 +59,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "14" - }, - "pubkey-chain": { - "vipObjectType": "tree", - "vipValue": [], - "vipType": "ignore", - "vipPrimaryKey": [ - "key-string" - ] } } ], @@ -136,16 +120,6 @@ "vipObjectType": "object", "vipType": "constant", "vipValue": "21" - }, - "secret-key": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" - }, - "key-enum": { - "vipObjectType": "object", - "vipValue": [], - "vipType": "ignore" } } ], @@ -158,14 +132,6 @@ "vipPrimaryKey": [ "group-name" ] - }, - "tacacs": { - "vipObjectType": "tree", - "vipValue": [], - "vipType": "ignore", - "vipPrimaryKey": [ - "group-name" - ] } } }