From 61285551c426169df14c0ff6fb8d48bf4a7e689a Mon Sep 17 00:00:00 2001 From: cicharka <93913624+cicharka@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:05:57 +0200 Subject: [PATCH] dev: Fix Template API and remove jinja2 (#776) * template info use string as template type * remove generation of Device template with jinja2 * catch non-existing template before attach operation --- catalystwan/api/template_api.py | 19 ++++---- .../device_template/device_template.py | 44 +++++++------------ .../device_template_payload.json.j2 | 19 -------- .../configuration_template_master.py | 15 ++++++- 4 files changed, 40 insertions(+), 57 deletions(-) delete mode 100644 catalystwan/api/templates/device_template/device_template_payload.json.j2 diff --git a/catalystwan/api/template_api.py b/catalystwan/api/template_api.py index b8b06fae..cf3b8585 100644 --- a/catalystwan/api/template_api.py +++ b/catalystwan/api/template_api.py @@ -118,11 +118,14 @@ def _get_device_templates( return self.session.endpoints.configuration_template_master.get_device_template_list(params=feature.value) def attach(self, name: str, device: Device, timeout_seconds: int = 300, **kwargs): - template_type = self.get(DeviceTemplate).filter(name=name).single_or_default().config_type - if template_type == TemplateType.CLI: + template_info = self.get(DeviceTemplate).filter(name=name).single_or_default() + if not template_info: + raise TemplateNotFoundError(f"Template with name [{name}] does not exists.") + + if template_info.config_type == TemplateType.CLI.value: return self._attach_cli(name, device, timeout_seconds=timeout_seconds, **kwargs) - if template_type == TemplateType.FEATURE: + if template_info.config_type == TemplateType.FEATURE.value: return self._attach_feature(name, device, timeout_seconds=timeout_seconds, **kwargs) raise NotImplementedError() @@ -495,18 +498,18 @@ def parse_general_template( .id ) payload = json.loads(device_template.generate_payload()) - response = self.session.put(f"/dataservice/template/device/{template_id}", json=payload) + response = self.session.endpoints.configuration_template_master.edit_template( + template_id=template_id, payload=payload + ) + return response else: - # endpoint = "/dataservice/template/device/feature/" - # response = self.session.post(endpoint, json=payload) payload = json.loads(device_template.generate_payload()) response = ( self.session.endpoints.configuration_template_master.create_device_template_from_feature_templates( payload=payload ) ) - - return response.text + return response.template_id def is_created_by_generator(self, template: FeatureTemplate) -> bool: """Checks if template is created by generator diff --git a/catalystwan/api/templates/device_template/device_template.py b/catalystwan/api/templates/device_template/device_template.py index 4af7a049..2a5d13d9 100644 --- a/catalystwan/api/templates/device_template/device_template.py +++ b/catalystwan/api/templates/device_template/device_template.py @@ -3,11 +3,9 @@ from __future__ import annotations import logging -from pathlib import Path -from typing import TYPE_CHECKING, ClassVar, List, Literal, Optional +from typing import TYPE_CHECKING, List, Literal, Optional from uuid import UUID -from jinja2 import DebugUndefined, Environment, FileSystemLoader, meta # type: ignore from pydantic import BaseModel, ConfigDict, Field, field_validator if TYPE_CHECKING: @@ -59,21 +57,19 @@ class DeviceTemplate(BaseModel): template_name: str = Field(serialization_alias="templateName", validation_alias="templateName") template_description: str = Field(serialization_alias="templateDescription", validation_alias="templateDescription") - general_templates: List[GeneralTemplate] = Field( - default=[], serialization_alias="generalTemplates", validation_alias="generalTemplates" - ) - device_role: str = Field(default="sdwan-edge", serialization_alias="deviceRole", validation_alias="deviceRole") device_type: str = Field(serialization_alias="deviceType", validation_alias="deviceType") - security_policy_id: str = Field( - default="", serialization_alias="securityPolicyId", validation_alias="securityPolicyId" + device_role: str = Field(default="sdwan-edge", serialization_alias="deviceRole", validation_alias="deviceRole") + config_type: Optional[str] = Field( + default="template", serialization_alias="configType", validation_alias="configType" + ) + factory_default: Optional[bool] = Field( + default=False, serialization_alias="factoryDefault", validation_alias="factoryDefault" ) policy_id: str = Field(default="", serialization_alias="policyId", validation_alias="policyId") feature_template_uid_range: Optional[List] = Field( default=[], serialization_alias="featureTemplateUidRange", validation_alias="featureTemplateUidRange" ) - config_type: Optional[str] = Field( - default="template", serialization_alias="configType", validation_alias="configType" - ) + connection_preference_required: Optional[bool] = Field( default=True, serialization_alias="connectionPreferenceRequired", @@ -82,8 +78,12 @@ class DeviceTemplate(BaseModel): connection_preference: Optional[bool] = Field( default=True, serialization_alias="connectionPreference", validation_alias="connectionPreference" ) - factory_default: Optional[bool] = Field( - default=False, serialization_alias="factoryDefault", validation_alias="factoryDefault" + + general_templates: List[GeneralTemplate] = Field( + default=[], serialization_alias="generalTemplates", validation_alias="generalTemplates" + ) + security_policy_id: str = Field( + default="", serialization_alias="securityPolicyId", validation_alias="securityPolicyId" ) def get_security_policy_uuid(self) -> Optional[UUID]: @@ -93,19 +93,7 @@ def get_policy_uuid(self) -> Optional[UUID]: return str_to_uuid(self.policy_id) def generate_payload(self) -> str: - env = Environment( - loader=FileSystemLoader(self.payload_path.parent), - trim_blocks=True, - lstrip_blocks=True, - undefined=DebugUndefined, - ) - template = env.get_template(self.payload_path.name) - output = template.render(self.model_dump()) - - ast = env.parse(output) - if meta.find_undeclared_variables(ast): - logger.info(meta.find_undeclared_variables(ast)) - raise Exception("There are undeclared variables.") + output = self.model_dump_json(by_alias=True) return output @field_validator("general_templates", mode="before") @@ -119,8 +107,6 @@ def parse_templates(cls, value): output.append(template) return output - payload_path: ClassVar[Path] = Path(__file__).parent / "device_template_payload.json.j2" - @classmethod def get(self, name: str, session: ManagerSession) -> DeviceTemplate: device_template = session.api.templates.get(DeviceTemplate).filter(name=name).single_or_default() diff --git a/catalystwan/api/templates/device_template/device_template_payload.json.j2 b/catalystwan/api/templates/device_template/device_template_payload.json.j2 deleted file mode 100644 index cd7debc3..00000000 --- a/catalystwan/api/templates/device_template/device_template_payload.json.j2 +++ /dev/null @@ -1,19 +0,0 @@ -{ - "templateName": "{{ template_name }}", - "templateDescription": "{{ template_description }}", - "deviceType": "{{ device_type }}", - "deviceRole": "{{ device_role }}", - "configType": "{{ config_type }}", - "factoryDefault": false, - "policyId": "{{ policy_id }}", - "featureTemplateUidRange": [], - "connectionPreferenceRequired": {{ connection_preference_required }}, - "connectionPreference": {{ connection_preference }}, - "generalTemplates": [ - {% for template in general_templates %} - {{ template | tojson }} - {% if not loop.last %},{% endif %} - {% endfor %} - ], - "securityPolicyId": "{{ security_policy_id }}" -} \ No newline at end of file diff --git a/catalystwan/endpoints/configuration_template_master.py b/catalystwan/endpoints/configuration_template_master.py index ece35326..8189251f 100644 --- a/catalystwan/endpoints/configuration_template_master.py +++ b/catalystwan/endpoints/configuration_template_master.py @@ -2,11 +2,12 @@ # mypy: disable-error-code="empty-body" +from typing import List, Optional from uuid import UUID from pydantic import BaseModel, ConfigDict, Field -from catalystwan.endpoints import JSON, APIEndpoints, get, post +from catalystwan.endpoints import JSON, APIEndpoints, get, post, put from catalystwan.models.templates import DeviceTemplateInformation, FeatureType from catalystwan.typed_list import DataSequence @@ -17,6 +18,14 @@ class TemplateID(BaseModel): template_id: UUID = Field(serialization_alias="templateId", validation_alias="templateId") +class AttachedDevices(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + attached_devices: Optional[List] = Field( + default=[], serialization_alias="AttachedDevices", validation_alias="AttachedDevices" + ) + + class FeatureQueryParams(BaseModel): model_config = ConfigDict(populate_by_name=True) @@ -33,3 +42,7 @@ def get_device_template_list( @post("/template/device/feature") def create_device_template_from_feature_templates(self, payload: JSON) -> TemplateID: ... + + @put("/template/device/{template_id}", "data") + def edit_template(self, template_id: str, payload: JSON) -> AttachedDevices: + ...