Skip to content

Commit c4bbcdb

Browse files
authored
cli_templates: modify existing CLI templates, cli_templates_info: new module (#54)
1 parent 0e7a5f2 commit c4bbcdb

File tree

2 files changed

+254
-2
lines changed

2 files changed

+254
-2
lines changed

plugins/modules/cli_templates.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@
3737
required: false
3838
type: str
3939
aliases: ["running_config_file_path"]
40+
force:
41+
description:
42+
- Update already existing templates. When template is attached to devices, reattach new version.
43+
required: false
44+
type: bool
45+
default: false
46+
timeout_seconds:
47+
description:
48+
- The timeout in seconds for attaching the template. Default is 300.
49+
type: int
4050
author:
4151
- Arkadiusz Cichon ([email protected])
4252
extends_documentation_fragment:
@@ -81,20 +91,31 @@
8191
sample: "abc123"
8292
"""
8393

84-
from typing import Literal, Optional, get_args
94+
from typing import List, Literal, Optional, get_args
8595

8696
from catalystwan.api.template_api import CLITemplate
97+
from catalystwan.api.templates.device_template.device_template import DeviceTemplateConfigAttached
98+
from catalystwan.dataclasses import Device
8799
from catalystwan.models.common import DeviceModel
88100
from catalystwan.models.templates import DeviceTemplateInformation
89101
from catalystwan.session import ManagerHTTPError
90102
from catalystwan.typed_list import DataSequence
103+
from ciscoconfparse import CiscoConfParse # type: ignore
104+
from pydantic import BaseModel, Field
91105

92106
from ..module_utils.result import ModuleResult
93107
from ..module_utils.vmanage_module import AnsibleCatalystwanModule
94108

95109
State = Literal["present", "absent"]
96110

97111

112+
class ExtendedManagerResponse(BaseModel):
113+
process_id: Optional[str] = Field(default=None, validation_alias="processId", serialization_alias="processId")
114+
attached_configs: Optional[List[DeviceTemplateConfigAttached]] = Field(
115+
default=None, validation_alias="attachedDevices", serialization_alias="attachedDevices"
116+
)
117+
118+
98119
def run_module():
99120
module_args = dict(
100121
state=dict(
@@ -106,6 +127,8 @@ def run_module():
106127
template_description=dict(type="str", default=None),
107128
device_model=dict(type="str", aliases=["device_type"], choices=list(get_args(DeviceModel)), default=None),
108129
config_file=dict(type="str", aliases=["running_config_file_path"]),
130+
force=dict(type="bool", default=False),
131+
timeout_seconds=dict(type="int", default=300),
109132
)
110133
result = ModuleResult()
111134

@@ -138,11 +161,57 @@ def run_module():
138161

139162
if module.params.get("state") == "present":
140163
# Code for checking if template name exists already
141-
if target_template:
164+
if target_template and not module.params.get("force"):
142165
module.logger.debug(f"Detected existing template:\n{target_template}\n")
143166
result.msg = (
144167
f"Template with name {template_name} already present on vManage, skipping create template operation."
145168
)
169+
elif target_template:
170+
current_template = module.get_response_safely(
171+
module.session.api.templates.get_device_template, template_id=target_template[0].id
172+
)
173+
current_template_configuration = CiscoConfParse(current_template.template_configuration.splitlines())
174+
175+
new_template = CLITemplate(
176+
template_name=template_name,
177+
template_description=module.params.get("template_description"),
178+
device_model=module.params.get("device_model"),
179+
)
180+
new_template_configuration = new_template.load_from_file(file=module.params.get("config_file"))
181+
182+
template_configuration_diff = CLITemplate.compare_template(
183+
current_template_configuration, new_template_configuration
184+
)
185+
if template_configuration_diff:
186+
module.logger.debug(f"Detected changes:\n{template_configuration_diff}\nTemplate will be updated")
187+
response = module.get_response_safely(module.session.api.templates.edit, template=new_template)
188+
template_config_attached = response.dataseq(ExtendedManagerResponse)[0].attached_configs
189+
all_devices: DataSequence[Device] = module.get_response_safely(module.session.api.devices.get)
190+
for attached_config in template_config_attached:
191+
module.logger.debug(
192+
f"Template is attached to device: {attached_config.uuid}\nReattaching new version"
193+
)
194+
device = all_devices.filter(uuid=attached_config.uuid)[0]
195+
module.get_response_safely(
196+
module.session.api.templates.edit_before_push, name=template_name, device=device
197+
)
198+
response = module.session.api.templates.attach(
199+
name=template_name,
200+
device=device,
201+
timeout_seconds=module.params.get("timeout_seconds"),
202+
is_edited=True,
203+
)
204+
result.changed = True
205+
result.msg = (
206+
f"Template with name {template_name} already present on vManage is different than provided."
207+
" Updating and reattaching."
208+
)
209+
else:
210+
module.logger.debug(f"Detected existing template:\n{target_template}\n")
211+
result.msg = (
212+
f"Template with name {template_name} already present on vManage and is the same as provided."
213+
" Skipping template update."
214+
)
146215
else:
147216
cli_template = CLITemplate(
148217
template_name=template_name,
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
4+
# Copyright 2025 Cisco Systems, Inc. and its affiliates
5+
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
6+
7+
8+
DOCUMENTATION = r"""
9+
---
10+
module: cli_templates_info
11+
short_description: Get information about CLI Templates on vManage.
12+
version_added: "0.3.5"
13+
description:
14+
- This module allows you to get CLI Templates Info from vManage.
15+
options:
16+
filters:
17+
description:
18+
- A dictionary of filters used to select CLI Templates info.
19+
type: dict
20+
required: false
21+
suboptions:
22+
device_type:
23+
description:
24+
- The device type of the template
25+
required: false
26+
default: null
27+
type: list
28+
elements: str
29+
name:
30+
description:
31+
- The name of the CLI Template.
32+
required: false
33+
default: null
34+
type: str
35+
description:
36+
description:
37+
- Description of the CLI Template.
38+
required: false
39+
default: null
40+
type: str
41+
version:
42+
description:
43+
- Version of the CLI Template.
44+
required: false
45+
default: null
46+
type: str
47+
factory_default:
48+
description:
49+
- If template is Factory Default template.
50+
required: false
51+
default: null
52+
type: bool
53+
draft_mode:
54+
description:
55+
- The draft mode of template.
56+
required: false
57+
default: null
58+
type: str
59+
device_role:
60+
description:
61+
- The device role.
62+
required: false
63+
default: null
64+
type: str
65+
id:
66+
description:
67+
- CLI Template ID.
68+
required: false
69+
default: null
70+
type: str
71+
last_updated_on:
72+
description:
73+
- Last Updated on value.
74+
required: false
75+
default: null
76+
type: int
77+
last_updated_by:
78+
description:
79+
- Last Updated by value.
80+
required: false
81+
default: null
82+
type: str
83+
resource_group:
84+
description:
85+
- Resource Group value.
86+
required: false
87+
default: null
88+
type: str
89+
gather_configuration:
90+
description:
91+
- Retrieve a templates_configuration list, including detailed configurations for each device in the result.
92+
type: bool
93+
default: False
94+
extends_documentation_fragment:
95+
- cisco.catalystwan.manager_authentication
96+
notes:
97+
- Ensure that the provided credentials have sufficient permissions to manage templates and devices in vManage.
98+
"""
99+
100+
EXAMPLES = r"""
101+
- name: Get all vmanage CLI Templates available
102+
cisco.catalystwan.cli_templates:
103+
filters:
104+
device_type: vmanage
105+
manager_credentials: ...
106+
"""
107+
108+
RETURN = r"""
109+
msg:
110+
description: A message describing the result of the operation.
111+
returned: always
112+
type: str
113+
sample: "Succesfully got all requested CLI Templates Info from vManage"
114+
changed:
115+
description: A boolean flag indicating if any changes were made.
116+
returned: always
117+
type: bool
118+
sample: true
119+
templates_info:
120+
description: List of CLI templates filtered by arguments provided to module.
121+
returned: always
122+
type: list
123+
templates_configuration:
124+
description: List of CLI templates detailed configuration filtered by arguments provided to module.
125+
returned: when gather_configuration is set
126+
type: list
127+
"""
128+
129+
from typing import Literal
130+
131+
from catalystwan.api.template_api import CLITemplate
132+
from catalystwan.api.templates.device_template.device_template import DeviceTemplate
133+
from catalystwan.models.templates import DeviceTemplateInformation
134+
from catalystwan.typed_list import DataSequence
135+
136+
from ..module_utils.result import ModuleResult
137+
from ..module_utils.vmanage_module import AnsibleCatalystwanModule
138+
139+
140+
def run_module():
141+
module_args = dict(
142+
filters=dict(type="dict", default=None, required=False),
143+
gather_configuration=dict(type="bool", default=False),
144+
)
145+
result = ModuleResult()
146+
147+
module = AnsibleCatalystwanModule(argument_spec=module_args)
148+
149+
filters = module.params.get("filters")
150+
151+
all_templates: DataSequence[DeviceTemplateInformation] = module.get_response_safely(
152+
module.session.api.templates.get, template=CLITemplate
153+
)
154+
155+
if module.params.get("filters"):
156+
result.templates_info = [template for template in all_templates.filter(**filters)]
157+
else:
158+
result.templates_info = [template for template in all_templates]
159+
160+
if module.params.get("gather_configuration"):
161+
result.templates_configuration = []
162+
for template in result.templates_info:
163+
detailed_template: DeviceTemplate = module.get_response_safely(
164+
module.session.api.templates.get_device_template, template_id=template.id
165+
)
166+
result.templates_configuration.append(detailed_template)
167+
168+
if result.templates_info:
169+
module.logger.info(f"All CLI Templates filtered with filters: {filters}:\n{result.templates_info}")
170+
result.msg = "Succesfully got all requested CLI Templates Info from vManage"
171+
else:
172+
module.logger.warning(msg=f"CLI templates filtered with `{filters}` not present.")
173+
result.msg = f"CLI templates filtered with `{filters}` not present on vManage."
174+
175+
module.exit_json(**result.model_dump(mode="json"))
176+
177+
178+
def main():
179+
run_module()
180+
181+
182+
if __name__ == "__main__":
183+
main()

0 commit comments

Comments
 (0)