Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Fr/feature templates #95

Merged
merged 46 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7190750
Add basic aaa templates
kagrski Dec 16, 2022
f407746
Add basic script
kagrski Dec 16, 2022
3ef635e
create radius j2
tzietkow Dec 16, 2022
a48fc5b
radius tacacs scripts for aaa feature template
tzietkow Dec 19, 2022
568c6f9
Fix User dataclass
kagrski Dec 19, 2022
ee25b1c
Move configuration jinja files
kagrski Jan 4, 2023
d0f7691
created vpn
tzietkow Jan 10, 2023
a14a453
created vpn
tzietkow Jan 10, 2023
6f2f5a4
Add TenantAPI.
kagrski Jan 10, 2023
b68bdb5
Merge branch 'fr/feature_templates' of https://github.com/CiscoDevNet…
kagrski Jan 10, 2023
37e50f2
working template
tzietkow Jan 11, 2023
5e664d5
Add TenantAPI.
kagrski Jan 11, 2023
bd2232d
Merge branch 'fr/feature_templates' of https://github.com/CiscoDevNet…
kagrski Jan 11, 2023
a2624ae
Rename template_api.
kagrski Jan 11, 2023
3899373
Rename template_api.
kagrski Jan 11, 2023
a28cdba
create dns for vpn
tzietkow Jan 11, 2023
506f4a8
added mapping
tzietkow Jan 11, 2023
3a1914c
Add TenantModel.
kagrski Jan 11, 2023
4ac1948
Add TenantModel.
kagrski Jan 11, 2023
6a55380
Add AAAModel.
kagrski Jan 11, 2023
c76da56
tojson
kagrski Jan 12, 2023
11bf21d
Add tenant payload
kagrski Jan 12, 2023
561a867
Align new model to pydantic.
kagrski Jan 12, 2023
9e721a8
adaptation to the new concept
tzietkow Jan 12, 2023
5fcd565
changed names with vpn to cisco_vpn
tzietkow Jan 12, 2023
0997140
added generate vpn id
tzietkow Jan 12, 2023
065d50d
added vpn ipv4routing next-hop
tzietkow Jan 12, 2023
e4ae12c
Finish Cisco VPN template
tzietkow Jan 16, 2023
e9cb7fb
Add path as a abstract property.
kagrski Jan 17, 2023
899b6b6
Overload generate_payload.
kagrski Jan 17, 2023
07e1a1b
Finish Tenant template.
kagrski Jan 18, 2023
7811c90
Fix AAAModel.
kagrski Jan 18, 2023
3244aa1
Fix static typing.
kagrski Jan 18, 2023
2da1ce3
Remove fr_templates.py
kagrski Jan 18, 2023
ad7eb42
Merge remote-tracking branch 'origin/main' into fr/feature_templates
kagrski Jan 18, 2023
823b35a
Revert changes.
kagrski Jan 18, 2023
c41318d
Use double-quotes
kagrski Jan 18, 2023
4e9639f
Fix unittests.
kagrski Jan 19, 2023
d4b70f3
fix test
tzietkow Jan 19, 2023
6551941
Fix mocks order.
kagrski Jan 19, 2023
b48ca57
Fix mocks order.
kagrski Jan 19, 2023
90a42e6
Add TemplateAlreadyExistsError.
kagrski Jan 19, 2023
f0141bb
Return template_id.
kagrski Jan 19, 2023
3188d53
Changed numeric value to enum
tzietkow Jan 20, 2023
808b69f
Update dependencies.
kagrski Jan 20, 2023
60ba2f8
Merge branch 'main' into fr/feature_templates
kagrski Jan 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,636 changes: 881 additions & 755 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ aiohttp = "^3.8.1"
ciscoconfparse = "^1.6.40"
tenacity = "^8.1.0"
parameterized = "^0.8.1"
cattrs = "^22.2.0"
pydantic = "^1.10.4"
Jinja2 = "^3.1.2"
flake8-quotes = "^3.3.1"
clint = "^0.5.1"
Expand Down
62 changes: 51 additions & 11 deletions vmngclient/api/templates.py → vmngclient/api/template_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import logging
from difflib import Differ
from enum import Enum
from typing import List
from typing import List, Optional

from ciscoconfparse import CiscoConfParse # type: ignore
from requests.exceptions import HTTPError

from vmngclient.api.task_status_api import wait_for_completed
from vmngclient.dataclasses import Device, Template
from vmngclient.api.templates.feature_template import FeatureTemplate
from vmngclient.dataclasses import Device, FeatureTemplateInformation, Template
from vmngclient.exceptions import InvalidOperationError
from vmngclient.session import vManageSession
from vmngclient.utils.creation_tools import create_dataclass
from vmngclient.utils.device_model import DeviceModel
Expand All @@ -22,14 +24,14 @@ class TemplateType(Enum):
FEATURE = "template"


class NotFoundError(Exception):
class TemplateNotFoundError(Exception):
"""Used when a template item is not found."""

def __init__(self, template):
self.message = f"No such template: '{template}'"


class NameAlreadyExistError(Exception):
class TemplateAlreadyExistsError(Exception):
"""Used when a template item exists."""

def __init__(self, name):
Expand All @@ -50,7 +52,7 @@ def __init__(self, name):
self.message = f"Template: {name} - wrong template type."


class TemplateAPI:
class TemplatesAPI:
def __init__(self, session: vManageSession) -> None:
self.session = session

Expand All @@ -72,15 +74,15 @@ def get(self, name: str) -> Template:
name (str): Name of template.
Raises:
NotFoundError: If template does not exist.
TemplateNotFoundError: If template does not exist.
Returns:
Template: Selected template.
"""
for template in self.templates:
if name == template.name:
return template
raise NotFoundError(name)
raise TemplateNotFoundError(name)

def get_id(self, name: str) -> str:
"""
Expand All @@ -106,7 +108,7 @@ def attach(self, name: str, device: Device) -> bool:
try:
template_id = self.get_id(name)
self.template_validation(template_id, device=device)
except NotFoundError:
except TemplateNotFoundError:
logger.error(f"Error, Template with name {name} not found on {device}.")
return False
except HTTPError as error:
Expand Down Expand Up @@ -199,21 +201,59 @@ def create(
config (CiscoConfParse): The config to device.
Raises:
NameAlreadyExistError: If such template name already exists.
TemplateAlreadyExistsError: If such template name already exists.
Returns:
bool: True if create template is successful, otherwise - False.
"""
try:
self.get(name)
logger.error(f"Error, Template with name: {name} exists.")
raise NameAlreadyExistError(name)
except NotFoundError:
raise TemplateAlreadyExistsError(name)
except TemplateNotFoundError:
cli_template = CLITemplate(self.session, device_model, name, description)
cli_template.config = config
logger.info(f"Template with name: {name} - created.")
return cli_template.send_to_device()

def create_feature_template(self, template: FeatureTemplate) -> str:
try:
self.get_single_feature_template(name=template.name)
except TemplateNotFoundError:
payload = template.generate_payload(self.session)
response = self.session.post("/dataservice/template/feature", json=json.loads(payload))
template_id = response.json()["templateId"]
logger.info(f"Template {template.name} was created successfully ({template_id}).")
return template_id
raise TemplateAlreadyExistsError(template.name)

def get_feature_templates(self, name: Optional[str] = None) -> List[FeatureTemplateInformation]:
"""Get feature template list.
Note: In a multitenant vManage system, this API is only available in the Provider view.
"""
payload = {"summary": "true"}
response = self.session.get("/dataservice/template/feature", params=payload)
parsed_response = response.json()["data"]
fr_templates = [
create_dataclass(FeatureTemplateInformation, feature_template) for feature_template in parsed_response
]

if name is None:
return fr_templates
return list(filter(lambda template: template.name == name, fr_templates))

def get_single_feature_template(self, name: str) -> FeatureTemplateInformation:
fr_templates = self.get_feature_templates(name=name)

if not fr_templates:
raise TemplateNotFoundError(name)

if len(fr_templates) > 1:
raise InvalidOperationError("The input sequence contains more than one element.")

return fr_templates[0]

def template_validation(self, id: str, device: Device) -> str:
"""Checking the template of the configuration on the machine.
Expand Down
41 changes: 41 additions & 0 deletions vmngclient/api/templates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Contains a list of feature templates.

These feature template models are used to create and modify the templates
on the vManage server.

In addition, they are used to convert CLI config into separate feature
templates in vManage.
"""

# Basic FeatureTemplate class
from vmngclient.api.templates.feature_template import FeatureTemplate

# AAA Templates
from vmngclient.api.templates.payloads.aaa.aaa_model import AAAModel

# Cisco VPN Templates
from vmngclient.api.templates.payloads.cisco_vpn.cisco_vpn_model import (
DNS,
CiscoVPNModel,
GatewayType,
IPv4Route,
IPv6Route,
Mapping,
NextHop,
)

# CEdge Templates
from vmngclient.api.templates.payloads.tenant.tenant_model import TenantModel

__all__ = [
"FeatureTemplate",
"TenantModel",
"AAAModel",
"CiscoVPNModel",
"DNS",
"Mapping",
"IPv4Route",
"IPv6Route",
"GatewayType",
"NextHop",
]
36 changes: 36 additions & 0 deletions vmngclient/api/templates/feature_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from abc import ABC, abstractmethod
from pathlib import Path

from jinja2 import DebugUndefined, Environment, FileSystemLoader, meta # type: ignore
from pydantic import BaseModel # type: ignore

from vmngclient.session import vManageSession


class FeatureTemplate(BaseModel, ABC):
name: str
description: str

def generate_payload(self, session: vManageSession) -> 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.dict())

ast = env.parse(output)
if meta.find_undeclared_variables(ast):
print(meta.find_undeclared_variables(ast))
raise Exception
return output

def generate_cli(self) -> str:
raise NotImplementedError()

@property
@abstractmethod
def payload_path(self) -> Path:
raise NotImplementedError()
89 changes: 89 additions & 0 deletions vmngclient/api/templates/payloads/aaa/aaa_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import ClassVar, List, Optional

from attr import define, field # type: ignore

from vmngclient.api.templates.feature_template import FeatureTemplate
from vmngclient.dataclasses import User


class AuthenticationOrder(Enum):
LOCAL = "local"
RADIUS = "radius"
TACACS = "tacacs"


class TacacsAuthenticationMethod(Enum):
PAP = "pap"


class Action(Enum):
ACCEPT = "accept"
DENY = "deny"


class VpnType(Enum):
VPN_TRANSPORT = 0
VPN_MANAGMENT = 512


# from vmngclient.third_parties
@define
class TacacsServer:
"""Default values from documentations."""

address: str
auth_port: int = field(default=49)
secret_key: Optional[str] = field(default=None)
source_interface: Optional[str] = field(default=None)
vpn: int = field(default=0)
priority: int = field(default=0)


@define
class RadiusServer:
"""Default values from documentations."""

address: str
secret_key: Optional[str] = field(default=None)
source_interface: Optional[str] = field(default=None)
acct_port: int = field(default=1813)
auth_port: int = field(default=1812)
tag: Optional[str] = field(default=None)
timeout: int = field(default=5)
vpn: int = field(default=0)
priority: int = field(default=0)


@define
class AuthTask:
name: str
default_action: Action = field(default=Action.ACCEPT)


class AAAModel(FeatureTemplate):
class Config:
arbitrary_types_allowed = True

payload_path: ClassVar[Path] = Path(__file__).parent / "feature" / "aaa.json.j2"

auth_order: List[AuthenticationOrder]
auth_fallback: bool
auth_disable_audit_logs: bool
auth_admin_order: bool
auth_disable_netconf_logs: bool
auth_radius_servers: List[str] = []

local_users: List[User] = []

accounting: bool = True

tacacs_authentication: TacacsAuthenticationMethod = TacacsAuthenticationMethod.PAP
tacacs_timeout: int = 5
tacacs_servers: List[TacacsServer] = []
radius_retransmit: int = 3
radius_timeout: int = 5
radius_servers: List[RadiusServer] = []
Loading