|
| 1 | +#!/usr/bin/python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +# Copyright: (c) 2025, Gaspard Micol (@gmicol) <gmicol@cisco.com> |
| 5 | + |
| 6 | +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) |
| 7 | + |
| 8 | +from __future__ import absolute_import, division, print_function |
| 9 | + |
| 10 | +__metaclass__ = type |
| 11 | + |
| 12 | +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} |
| 13 | + |
| 14 | +DOCUMENTATION = r""" |
| 15 | +--- |
| 16 | +module: ndo_ntp_policy |
| 17 | +short_description: Manage NTP Policies in Fabric Policy Templates on Cisco Nexus Dashboard Orchestrator (NDO). |
| 18 | +description: |
| 19 | +- Manage NTP (NTP) Policies in Fabric Policy Templates on Cisco Nexus Dashboard Orchestrator (NDO). |
| 20 | +- This module is only supported on ND v3.1 (NDO v4.3) and later. |
| 21 | +author: |
| 22 | +- Gaspard Micol (@gmicol) |
| 23 | +options: |
| 24 | + template: |
| 25 | + description: |
| 26 | + - The name of the Fabric Policy template. |
| 27 | + type: str |
| 28 | + aliases: [ fabric_policy_template ] |
| 29 | + required: true |
| 30 | + name: |
| 31 | + description: |
| 32 | + - The name of the NTP Policy. |
| 33 | + type: str |
| 34 | + aliases: [ ntp_policy ] |
| 35 | + uuid: |
| 36 | + description: |
| 37 | + - The UUID of the NTP Policy. |
| 38 | + - This parameter is required when the NTP Policy O(name) needs to be updated. |
| 39 | + aliases: [ ntp_policy_uuid ] |
| 40 | + type: str |
| 41 | + description: |
| 42 | + description: |
| 43 | + - The description of the NTP Policy. |
| 44 | + - Providing an empty string will remove the O(description="") from the NTP Policy. |
| 45 | + type: str |
| 46 | + ntp_keys: |
| 47 | + description: |
| 48 | + - The List of NTP client authentication keys. |
| 49 | + - Providing a new list of O(ntp_keys) will completely replace an existing one from the NTP Policy. |
| 50 | + - Providing an empty list will remove the O(ntp_keys=[]) from the NTP Policy. |
| 51 | + type: list |
| 52 | + elements: dict |
| 53 | + suboptions: |
| 54 | + id: |
| 55 | + description: |
| 56 | + - The key's ID. |
| 57 | + type: int |
| 58 | + aliases: [ key_id ] |
| 59 | + key: |
| 60 | + description: |
| 61 | + - The key. |
| 62 | + type: str |
| 63 | + authentification_type: |
| 64 | + description: |
| 65 | + - the type of authentification. |
| 66 | + type: str |
| 67 | + choices: [ md5, sha1 ] |
| 68 | + trusted: |
| 69 | + description: |
| 70 | + - Set the NTP client authentification key to trusted. |
| 71 | + type: bool |
| 72 | + ntp_providers: |
| 73 | + description: |
| 74 | + - The list of NTP providers. |
| 75 | + - Providing a new list of O(ntp_providers) will completely replace an existing one from the NTP Policy. |
| 76 | + - Providing an empty list will remove the O(ntp_providers=[]) from the NTP Policy. |
| 77 | + type: list |
| 78 | + elements: dict |
| 79 | + suboptions: |
| 80 | + host: |
| 81 | + description: |
| 82 | + - The Hostname or IP address of the NTP provider. |
| 83 | + type: str |
| 84 | + minimum_poll_interval: |
| 85 | + description: |
| 86 | + - The Minimum Polling interval value. |
| 87 | + type: int |
| 88 | + aliases: [ min_poll ] |
| 89 | + maximum_poll_interval: |
| 90 | + description: |
| 91 | + - The Maximum Polling interval value. |
| 92 | + type: int |
| 93 | + aliases: [ max_poll ] |
| 94 | + management_epg_type: |
| 95 | + description: |
| 96 | + - The type of the management EPG. |
| 97 | + type: str |
| 98 | + choices: [ inb, oob ] |
| 99 | + aliases: [ epg_type ] |
| 100 | + management_epg: |
| 101 | + description: |
| 102 | + - The management EPG. |
| 103 | + type: str |
| 104 | + aliases: [ epg ] |
| 105 | + preferred: |
| 106 | + description: |
| 107 | + - Set the NTP provider to preferred. |
| 108 | + type: bool |
| 109 | + authentification_key_id: |
| 110 | + description: |
| 111 | + - The NTP authentification key ID. |
| 112 | + type: int |
| 113 | + aliases: [ key_id ] |
| 114 | + admin_state: |
| 115 | + description: |
| 116 | + - Enable admin state. |
| 117 | + type: str |
| 118 | + choices: [ enabled, disabled ] |
| 119 | + server_state: |
| 120 | + description: |
| 121 | + - Enable server state. |
| 122 | + type: str |
| 123 | + choices: [ enabled, disabled ] |
| 124 | + master_mode: |
| 125 | + description: |
| 126 | + - Enable master mode. |
| 127 | + type: str |
| 128 | + choices: [ enabled, disabled ] |
| 129 | + stratum: |
| 130 | + description: |
| 131 | + - The numerical value of the stratum. |
| 132 | + type: int |
| 133 | + authentification_state: |
| 134 | + description: |
| 135 | + - Enable authentification state. |
| 136 | + type: str |
| 137 | + choices: [ enabled, disabled ] |
| 138 | + state: |
| 139 | + description: |
| 140 | + - Use C(absent) for removing. |
| 141 | + - Use C(query) for listing an object or multiple objects. |
| 142 | + - Use C(present) for creating or updating. |
| 143 | + type: str |
| 144 | + choices: [ absent, query, present ] |
| 145 | + default: query |
| 146 | +notes: |
| 147 | +- The O(template) must exist before using this module in your playbook. |
| 148 | + Use M(cisco.mso.ndo_template) to create the Fabric Policy template. |
| 149 | +seealso: |
| 150 | +- module: cisco.mso.ndo_template |
| 151 | +extends_documentation_fragment: cisco.mso.modules |
| 152 | +""" |
| 153 | + |
| 154 | +EXAMPLES = r""" |
| 155 | +- name: Create a new NTP Policy object |
| 156 | + cisco.mso.ndo_ntp_policy: |
| 157 | + host: mso_host |
| 158 | + username: admin |
| 159 | + password: SomeSecretPassword |
| 160 | + template: fabric_policy_template |
| 161 | + name: ntp_policy_1 |
| 162 | + ntp_keys: |
| 163 | + - id: 1 |
| 164 | + key: my_key |
| 165 | + authentification_type: md5 |
| 166 | + trusted: true |
| 167 | + ntp_providers: |
| 168 | + - host: background |
| 169 | + minimum_poll_interval: 4 |
| 170 | + maximum_poll_interval: 16 |
| 171 | + management_epg_type: oob |
| 172 | + management_epg: default |
| 173 | + preferred: true |
| 174 | + authentification_key_id: 1 |
| 175 | + admin_state: enabled |
| 176 | + server_state: enabled |
| 177 | + master_mode: enabled |
| 178 | + stratum: 4 |
| 179 | + authentification_state: enabled |
| 180 | + state: present |
| 181 | + register: ntp_policy_1 |
| 182 | +
|
| 183 | +- name: Update a NTP Policy object name with UUID |
| 184 | + cisco.mso.ndo_ntp_policy: |
| 185 | + host: mso_host |
| 186 | + username: admin |
| 187 | + password: SomeSecretPassword |
| 188 | + template: fabric_policy_template |
| 189 | + name: ntp_policy_2 |
| 190 | + uuid: "{{ ntp_policy_1.current.uuid }}" |
| 191 | + state: present |
| 192 | +
|
| 193 | +- name: Query a NTP Policy object with name |
| 194 | + cisco.mso.ndo_ntp_policy: |
| 195 | + host: mso_host |
| 196 | + username: admin |
| 197 | + password: SomeSecretPassword |
| 198 | + template: fabric_policy_template |
| 199 | + name: ntp_policy_2 |
| 200 | + state: query |
| 201 | + register: query_name |
| 202 | +
|
| 203 | +- name: Query a NTP Policy object with UUID |
| 204 | + cisco.mso.ndo_ntp_policy: |
| 205 | + host: mso_host |
| 206 | + username: admin |
| 207 | + password: SomeSecretPassword |
| 208 | + template: fabric_policy_template |
| 209 | + uuid: "{{ ntp_policy_1.current.uuid }}" |
| 210 | + state: query |
| 211 | + register: query_uuid |
| 212 | +
|
| 213 | +- name: Query all NTP Policy objects in a Fabric Policy Template |
| 214 | + cisco.mso.ndo_ntp_policy: |
| 215 | + host: mso_host |
| 216 | + username: admin |
| 217 | + password: SomeSecretPassword |
| 218 | + template: fabric_policy_template |
| 219 | + state: query |
| 220 | + register: query_all |
| 221 | +
|
| 222 | +- name: Delete a NTP Policy object with name |
| 223 | + cisco.mso.ndo_ntp_policy: |
| 224 | + host: mso_host |
| 225 | + username: admin |
| 226 | + password: SomeSecretPassword |
| 227 | + template: fabric_policy_template |
| 228 | + name: ntp_policy_2 |
| 229 | + state: absent |
| 230 | +
|
| 231 | +- name: Delete a NTP Policy object with UUID |
| 232 | + cisco.mso.ndo_ntp_policy: |
| 233 | + host: mso_host |
| 234 | + username: admin |
| 235 | + password: SomeSecretPassword |
| 236 | + template: fabric_policy_template |
| 237 | + uuid: "{{ ntp_policy_1.current.uuid }}" |
| 238 | + state: absent |
| 239 | +""" |
| 240 | + |
| 241 | +RETURN = r""" |
| 242 | +""" |
| 243 | + |
| 244 | +import copy |
| 245 | +from ansible.module_utils.basic import AnsibleModule |
| 246 | +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec |
| 247 | +from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate, KVPair |
| 248 | +from ansible_collections.cisco.mso.plugins.module_utils.utils import append_update_ops_data |
| 249 | + |
| 250 | + |
| 251 | +def main(): |
| 252 | + argument_spec = mso_argument_spec() |
| 253 | + argument_spec.update( |
| 254 | + template=dict(type="str", required=True, aliases=["fabric_policy_template"]), |
| 255 | + name=dict(type="str", aliases=["ntp_policy"]), |
| 256 | + uuid=dict(type="str", aliases=["ntp_policy_uuid"]), |
| 257 | + description=dict(type="str"), |
| 258 | + ntp_keys=dict( |
| 259 | + type="list", |
| 260 | + elements="dict", |
| 261 | + options=dict( |
| 262 | + id=dict(type="int", aliases=["key_id"]), |
| 263 | + key=dict(type="str", no_log=True), |
| 264 | + authentification_type=dict(type="str", choices=["md5", "sha1"]), |
| 265 | + trusted=dict(type="bool"), |
| 266 | + ), |
| 267 | + no_log=False, |
| 268 | + ), |
| 269 | + ntp_providers=dict( |
| 270 | + type="list", |
| 271 | + elements="dict", |
| 272 | + options=dict( |
| 273 | + host=dict(type="str"), |
| 274 | + minimum_poll_interval=dict(type="int", aliases=["min_poll"]), |
| 275 | + maximum_poll_interval=dict(type="int", aliases=["max_poll"]), |
| 276 | + management_epg_type=dict(type="str", choices=["inb", "oob"], aliases=["epg_type"]), |
| 277 | + management_epg=dict(type="str", aliases=["epg"]), |
| 278 | + preferred=dict(type="bool"), |
| 279 | + authentification_key_id=dict(type="int", aliases=["key_id"]), |
| 280 | + ), |
| 281 | + ), |
| 282 | + admin_state=dict(type="str", choices=["enabled", "disabled"]), |
| 283 | + server_state=dict(type="str", choices=["enabled", "disabled"]), |
| 284 | + master_mode=dict(type="str", choices=["enabled", "disabled"]), |
| 285 | + stratum=dict(type="int"), |
| 286 | + authentification_state=dict(type="str", choices=["enabled", "disabled"]), |
| 287 | + state=dict(type="str", default="query", choices=["absent", "query", "present"]), |
| 288 | + ) |
| 289 | + |
| 290 | + module = AnsibleModule( |
| 291 | + argument_spec=argument_spec, |
| 292 | + supports_check_mode=True, |
| 293 | + required_if=[ |
| 294 | + ["state", "absent", ["name", "uuid"], True], |
| 295 | + ["state", "present", ["name", "uuid"], True], |
| 296 | + ], |
| 297 | + ) |
| 298 | + |
| 299 | + mso = MSOModule(module) |
| 300 | + |
| 301 | + template = module.params.get("template") |
| 302 | + name = module.params.get("name") |
| 303 | + uuid = module.params.get("uuid") |
| 304 | + description = module.params.get("description") |
| 305 | + ntp_keys = module.params.get("ntp_keys") |
| 306 | + if ntp_keys: |
| 307 | + ntp_keys = [ |
| 308 | + { |
| 309 | + "id": item.get("id"), |
| 310 | + "key": item.get("key"), |
| 311 | + "authType": item.get("authentification_type"), |
| 312 | + "trusted": item.get("trusted"), |
| 313 | + } |
| 314 | + for item in ntp_keys |
| 315 | + ] |
| 316 | + ntp_providers = module.params.get("ntp_providers") |
| 317 | + if ntp_providers: |
| 318 | + ntp_providers = [ |
| 319 | + { |
| 320 | + "host": item.get("host"), |
| 321 | + "minPollInterval": item.get("minimum_poll_interval"), |
| 322 | + "maxPollInterval": item.get("maximum_poll_interval"), |
| 323 | + "mgmtEpgType": item.get("management_epg_type"), |
| 324 | + "mgmtEpgName": item.get("management_epg"), |
| 325 | + "preferred": item.get("preferred"), |
| 326 | + "authKeyID": item.get("authentification_key_id"), |
| 327 | + } |
| 328 | + for item in ntp_providers |
| 329 | + ] |
| 330 | + admin_state = module.params.get("admin_state") |
| 331 | + server_state = module.params.get("server_state") |
| 332 | + master_mode = module.params.get("master_mode") |
| 333 | + stratum = module.params.get("stratum") |
| 334 | + authentification_state = module.params.get("authentification_state") |
| 335 | + state = module.params.get("state") |
| 336 | + |
| 337 | + template_object = MSOTemplate(mso, "tenant", template) |
| 338 | + template_object.validate_template("tenantPolicy") |
| 339 | + |
| 340 | + ntp_policies = template_object.template.get("fabricPolicyTemplate", {}).get("template", {}).get("ntpPolicies", []) |
| 341 | + object_description = "NTP Policy" |
| 342 | + |
| 343 | + if state in ["query", "absent"] and ntp_policies == []: |
| 344 | + mso.exit_json() |
| 345 | + elif state == "query" and not (name or uuid): |
| 346 | + mso.existing = ntp_policies |
| 347 | + elif ntp_policies and (name or uuid): |
| 348 | + match = template_object.get_object_by_key_value_pairs(object_description, ntp_policies, [KVPair("uuid", uuid) if uuid else KVPair("name", name)]) |
| 349 | + if match: |
| 350 | + ntp_policy_attrs_path = "/fabricPolicyTemplate/template/ntpPolicies/{0}".format(match.index) |
| 351 | + mso.existing = mso.previous = copy.deepcopy(match.details) |
| 352 | + |
| 353 | + ops = [] |
| 354 | + |
| 355 | + if state == "present": |
| 356 | + if uuid and not mso.existing: |
| 357 | + mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) |
| 358 | + |
| 359 | + mso_values = dict( |
| 360 | + name=name, |
| 361 | + description=description, |
| 362 | + ntpKeys=ntp_keys, |
| 363 | + ntpProviders=ntp_providers, |
| 364 | + adminState=admin_state, |
| 365 | + serverState=server_state, |
| 366 | + masterMode=master_mode, |
| 367 | + stratum=stratum, |
| 368 | + authState=authentification_state, |
| 369 | + ) |
| 370 | + |
| 371 | + if mso.existing: |
| 372 | + append_update_ops_data(ops, match.details, ntp_policy_attrs_path, mso_values) |
| 373 | + mso.sanitize(match.details, collate=True) |
| 374 | + else: |
| 375 | + mso.sanitize(mso_values) |
| 376 | + ops.append(dict(op="add", path="/fabricPolicyTemplate/template/ntpPolicies/-", value=mso.sent)) |
| 377 | + |
| 378 | + elif state == "absent": |
| 379 | + if mso.existing: |
| 380 | + ops.append(dict(op="remove", path=ntp_policy_attrs_path)) |
| 381 | + |
| 382 | + if not module.check_mode and ops: |
| 383 | + response_object = mso.request(template_object.template_path, method="PATCH", data=ops) |
| 384 | + ntp_policies = response_object.get("fabricPolicyTemplate", {}).get("template", {}).get("ntpPolicies", []) |
| 385 | + match = template_object.get_object_by_key_value_pairs(object_description, ntp_policies, [KVPair("uuid", uuid) if uuid else KVPair("name", name)]) |
| 386 | + if match: |
| 387 | + mso.existing = match.details # When the state is present |
| 388 | + else: |
| 389 | + mso.existing = {} # When the state is absent |
| 390 | + elif module.check_mode and state != "query": # When the state is present/absent with check mode |
| 391 | + mso.existing = mso.proposed if state == "present" else {} |
| 392 | + |
| 393 | + mso.exit_json() |
| 394 | + |
| 395 | + |
| 396 | +if __name__ == "__main__": |
| 397 | + main() |
0 commit comments