1""" 2NAPALM Network 3============== 4 5Basic methods for interaction with the network device through the virtual proxy 'napalm'. 6 7:codeauthor: Mircea Ulinic <ping@mirceaulinic.net> & Jerome Fleury <jf@cloudflare.com> 8:maturity: new 9:depends: napalm 10:platform: unix 11 12Dependencies 13------------ 14 15- :mod:`napalm proxy minion <salt.proxy.napalm>` 16 17.. versionadded:: 2016.11.0 18.. versionchanged:: 2017.7.0 19""" 20 21import datetime 22import logging 23import time 24 25import salt.utils.files 26import salt.utils.napalm 27import salt.utils.templates 28import salt.utils.versions 29 30log = logging.getLogger(__name__) 31 32 33try: 34 import jxmlease # pylint: disable=unused-import 35 36 HAS_JXMLEASE = True 37except ImportError: 38 HAS_JXMLEASE = False 39 40# ---------------------------------------------------------------------------------------------------------------------- 41# module properties 42# ---------------------------------------------------------------------------------------------------------------------- 43 44__virtualname__ = "net" 45__proxyenabled__ = ["*"] 46__virtual_aliases__ = ("napalm_net",) 47# uses NAPALM-based proxy to interact with network devices 48 49# ---------------------------------------------------------------------------------------------------------------------- 50# property functions 51# ---------------------------------------------------------------------------------------------------------------------- 52 53 54def __virtual__(): 55 """ 56 NAPALM library must be installed for this module to work and run in a (proxy) minion. 57 """ 58 return salt.utils.napalm.virtual(__opts__, __virtualname__, __file__) 59 60 61# ---------------------------------------------------------------------------------------------------------------------- 62# helper functions -- will not be exported 63# ---------------------------------------------------------------------------------------------------------------------- 64 65 66def _filter_list(input_list, search_key, search_value): 67 68 """ 69 Filters a list of dictionary by a set of key-value pair. 70 71 :param input_list: is a list of dictionaries 72 :param search_key: is the key we are looking for 73 :param search_value: is the value we are looking for the key specified in search_key 74 :return: filered list of dictionaries 75 """ 76 77 output_list = list() 78 79 for dictionary in input_list: 80 if dictionary.get(search_key) == search_value: 81 output_list.append(dictionary) 82 83 return output_list 84 85 86def _filter_dict(input_dict, search_key, search_value): 87 88 """ 89 Filters a dictionary of dictionaries by a key-value pair. 90 91 :param input_dict: is a dictionary whose values are lists of dictionaries 92 :param search_key: is the key in the leaf dictionaries 93 :param search_values: is the value in the leaf dictionaries 94 :return: filtered dictionary 95 """ 96 97 output_dict = dict() 98 99 for key, key_list in input_dict.items(): 100 key_list_filtered = _filter_list(key_list, search_key, search_value) 101 if key_list_filtered: 102 output_dict[key] = key_list_filtered 103 104 return output_dict 105 106 107def _safe_commit_config(loaded_result, napalm_device): 108 _commit = commit( 109 inherit_napalm_device=napalm_device 110 ) # calls the function commit, defined below 111 if not _commit.get("result", False): 112 # if unable to commit 113 loaded_result["comment"] += ( 114 _commit["comment"] if _commit.get("comment") else "Unable to commit." 115 ) 116 loaded_result["result"] = False 117 # unable to commit, something went wrong 118 discarded = _safe_dicard_config(loaded_result, napalm_device) 119 if not discarded["result"]: 120 return loaded_result 121 return _commit 122 123 124def _safe_dicard_config(loaded_result, napalm_device): 125 log.debug("Discarding the config") 126 log.debug(loaded_result) 127 _discarded = discard_config(inherit_napalm_device=napalm_device) 128 if not _discarded.get("result", False): 129 loaded_result["comment"] += ( 130 _discarded["comment"] 131 if _discarded.get("comment") 132 else "Unable to discard config." 133 ) 134 loaded_result["result"] = False 135 # make sure it notifies 136 # that something went wrong 137 _explicit_close(napalm_device) 138 __context__["retcode"] = 1 139 return loaded_result 140 return _discarded 141 142 143def _explicit_close(napalm_device): 144 """ 145 Will explicily close the config session with the network device, 146 when running in a now-always-alive proxy minion or regular minion. 147 This helper must be used in configuration-related functions, 148 as the session is preserved and not closed before making any changes. 149 """ 150 if salt.utils.napalm.not_always_alive(__opts__): 151 # force closing the configuration session 152 # when running in a non-always-alive proxy 153 # or regular minion 154 try: 155 napalm_device["DRIVER"].close() 156 except Exception as err: # pylint: disable=broad-except 157 log.error("Unable to close the temp connection with the device:") 158 log.error(err) 159 log.error("Please report.") 160 161 162def _config_logic( 163 napalm_device, 164 loaded_result, 165 test=False, 166 debug=False, 167 replace=False, 168 commit_config=True, 169 loaded_config=None, 170 commit_in=None, 171 commit_at=None, 172 revert_in=None, 173 revert_at=None, 174 commit_jid=None, 175 **kwargs 176): 177 """ 178 Builds the config logic for `load_config` and `load_template` functions. 179 """ 180 # As the Salt logic is built around independent events 181 # when it comes to configuration changes in the 182 # candidate DB on the network devices, we need to 183 # make sure we're using the same session. 184 # Hence, we need to pass the same object around. 185 # the napalm_device object is inherited from 186 # the load_config or load_template functions 187 # and forwarded to compare, discard, commit etc. 188 # then the decorator will make sure that 189 # if not proxy (when the connection is always alive) 190 # and the `inherit_napalm_device` is set, 191 # `napalm_device` will be overridden. 192 # See `salt.utils.napalm.proxy_napalm_wrap` decorator. 193 194 current_jid = kwargs.get("__pub_jid") 195 if not current_jid: 196 current_jid = "{:%Y%m%d%H%M%S%f}".format(datetime.datetime.now()) 197 198 loaded_result["already_configured"] = False 199 200 loaded_result["loaded_config"] = "" 201 if debug: 202 loaded_result["loaded_config"] = loaded_config 203 204 _compare = compare_config(inherit_napalm_device=napalm_device) 205 if _compare.get("result", False): 206 loaded_result["diff"] = _compare.get("out") 207 loaded_result.pop("out", "") # not needed 208 else: 209 loaded_result["diff"] = None 210 loaded_result["result"] = False 211 loaded_result["comment"] = _compare.get("comment") 212 __context__["retcode"] = 1 213 return loaded_result 214 215 _loaded_res = loaded_result.get("result", False) 216 if not _loaded_res or test: 217 # if unable to load the config (errors / warnings) 218 # or in testing mode, 219 # will discard the config 220 if loaded_result["comment"]: 221 loaded_result["comment"] += "\n" 222 if not len(loaded_result.get("diff", "")) > 0: 223 loaded_result["already_configured"] = True 224 discarded = _safe_dicard_config(loaded_result, napalm_device) 225 if not discarded["result"]: 226 return loaded_result 227 loaded_result["comment"] += "Configuration discarded." 228 # loaded_result['result'] = False not necessary 229 # as the result can be true when test=True 230 _explicit_close(napalm_device) 231 if not loaded_result["result"]: 232 __context__["retcode"] = 1 233 return loaded_result 234 235 if not test and commit_config: 236 # if not in testing mode and trying to commit 237 if commit_jid: 238 log.info("Committing the JID: %s", str(commit_jid)) 239 removed = cancel_commit(commit_jid) 240 log.debug("Cleaned up the commit from the schedule") 241 log.debug(removed["comment"]) 242 if len(loaded_result.get("diff", "")) > 0: 243 # if not testing mode 244 # and also the user wants to commit (default) 245 # and there are changes to commit 246 if commit_in or commit_at: 247 commit_time = __utils__["timeutil.get_time_at"]( 248 time_in=commit_in, time_at=commit_in 249 ) 250 # schedule job 251 scheduled_job_name = "__napalm_commit_{}".format(current_jid) 252 temp_file = salt.utils.files.mkstemp() 253 with salt.utils.files.fopen(temp_file, "w") as fp_: 254 fp_.write(loaded_config) 255 scheduled = __salt__["schedule.add"]( 256 scheduled_job_name, 257 function="net.load_config", 258 job_kwargs={ 259 "filename": temp_file, 260 "commit_jid": current_jid, 261 "replace": replace, 262 }, 263 once=commit_time, 264 ) 265 log.debug("Scheduling job") 266 log.debug(scheduled) 267 saved = __salt__["schedule.save"]() # ensure the schedule is 268 # persistent cross Minion restart 269 discarded = _safe_dicard_config(loaded_result, napalm_device) 270 # discard the changes 271 if not discarded["result"]: 272 discarded["comment"] += ( 273 "Scheduled the job to be executed at {schedule_ts}, " 274 "but was unable to discard the config: \n".format( 275 schedule_ts=commit_time 276 ) 277 ) 278 return discarded 279 loaded_result["comment"] = ( 280 "Changes discarded for now, and scheduled commit at:" 281 " {schedule_ts}.\nThe commit ID is: {current_jid}.\nTo discard this" 282 " commit, you can execute: \n\nsalt {min_id} net.cancel_commit" 283 " {current_jid}".format( 284 schedule_ts=commit_time, 285 min_id=__opts__["id"], 286 current_jid=current_jid, 287 ) 288 ) 289 loaded_result["commit_id"] = current_jid 290 return loaded_result 291 log.debug("About to commit:") 292 log.debug(loaded_result["diff"]) 293 if revert_in or revert_at: 294 revert_time = __utils__["timeutil.get_time_at"]( 295 time_in=revert_in, time_at=revert_at 296 ) 297 if __grains__["os"] == "junos": 298 if not HAS_JXMLEASE: 299 loaded_result["comment"] = ( 300 "This feature requires the library jxmlease to be" 301 " installed.\nTo install, please execute: ``pip install" 302 " jxmlease``." 303 ) 304 loaded_result["result"] = False 305 return loaded_result 306 timestamp_at = __utils__["timeutil.get_timestamp_at"]( 307 time_in=revert_in, time_at=revert_at 308 ) 309 minutes = int((timestamp_at - time.time()) / 60) 310 _comm = __salt__["napalm.junos_commit"](confirm=minutes) 311 if not _comm["out"]: 312 # If unable to commit confirm, should try to bail out 313 loaded_result[ 314 "comment" 315 ] = "Unable to commit confirm: {}".format(_comm["message"]) 316 loaded_result["result"] = False 317 # But before exiting, we must gracefully discard the config 318 discarded = _safe_dicard_config(loaded_result, napalm_device) 319 if not discarded["result"]: 320 return loaded_result 321 else: 322 temp_file = salt.utils.files.mkstemp() 323 running_config = __salt__["net.config"](source="running")["out"][ 324 "running" 325 ] 326 with salt.utils.files.fopen(temp_file, "w") as fp_: 327 fp_.write(running_config) 328 committed = _safe_commit_config(loaded_result, napalm_device) 329 if not committed["result"]: 330 # If unable to commit, dicard the config (which is 331 # already done by the _safe_commit_config function), and 332 # return with the command and other details. 333 return loaded_result 334 scheduled_job_name = "__napalm_commit_{}".format(current_jid) 335 scheduled = __salt__["schedule.add"]( 336 scheduled_job_name, 337 function="net.load_config", 338 job_kwargs={ 339 "filename": temp_file, 340 "commit_jid": current_jid, 341 "replace": True, 342 }, 343 once=revert_time, 344 ) 345 log.debug("Scheduling commit confirmed") 346 log.debug(scheduled) 347 saved = __salt__["schedule.save"]() 348 loaded_result["comment"] = ( 349 "The commit ID is: {current_jid}.\nThis commit will be reverted at:" 350 " {schedule_ts}, unless confirmed.\nTo confirm the commit and avoid" 351 " reverting, you can execute:\n\nsalt {min_id} net.confirm_commit" 352 " {current_jid}".format( 353 schedule_ts=revert_time, 354 min_id=__opts__["id"], 355 current_jid=current_jid, 356 ) 357 ) 358 loaded_result["commit_id"] = current_jid 359 return loaded_result 360 committed = _safe_commit_config(loaded_result, napalm_device) 361 if not committed["result"]: 362 return loaded_result 363 else: 364 # would like to commit, but there's no change 365 # need to call discard_config() to release the config DB 366 discarded = _safe_dicard_config(loaded_result, napalm_device) 367 if not discarded["result"]: 368 return loaded_result 369 loaded_result["already_configured"] = True 370 loaded_result["comment"] = "Already configured." 371 _explicit_close(napalm_device) 372 if not loaded_result["result"]: 373 __context__["retcode"] = 1 374 return loaded_result 375 376 377# ---------------------------------------------------------------------------------------------------------------------- 378# callable functions 379# ---------------------------------------------------------------------------------------------------------------------- 380 381 382@salt.utils.napalm.proxy_napalm_wrap 383def connected(**kwargs): # pylint: disable=unused-argument 384 """ 385 Specifies if the connection to the device succeeded. 386 387 CLI Example: 388 389 .. code-block:: bash 390 391 salt '*' net.connected 392 """ 393 394 return {"out": napalm_device.get("UP", False)} # pylint: disable=undefined-variable 395 396 397@salt.utils.napalm.proxy_napalm_wrap 398def facts(**kwargs): # pylint: disable=unused-argument 399 """ 400 Returns characteristics of the network device. 401 :return: a dictionary with the following keys: 402 403 * uptime - Uptime of the device in seconds. 404 * vendor - Manufacturer of the device. 405 * model - Device model. 406 * hostname - Hostname of the device 407 * fqdn - Fqdn of the device 408 * os_version - String with the OS version running on the device. 409 * serial_number - Serial number of the device 410 * interface_list - List of the interfaces of the device 411 412 CLI Example: 413 414 .. code-block:: bash 415 416 salt '*' net.facts 417 418 Example output: 419 420 .. code-block:: python 421 422 { 423 'os_version': '13.3R6.5', 424 'uptime': 10117140, 425 'interface_list': [ 426 'lc-0/0/0', 427 'pfe-0/0/0', 428 'pfh-0/0/0', 429 'xe-0/0/0', 430 'xe-0/0/1', 431 'xe-0/0/2', 432 'xe-0/0/3', 433 'gr-0/0/10', 434 'ip-0/0/10' 435 ], 436 'vendor': 'Juniper', 437 'serial_number': 'JN131356FBFA', 438 'model': 'MX480', 439 'hostname': 're0.edge05.syd01', 440 'fqdn': 're0.edge05.syd01' 441 } 442 """ 443 444 return salt.utils.napalm.call( 445 napalm_device, "get_facts", **{} # pylint: disable=undefined-variable 446 ) 447 448 449@salt.utils.napalm.proxy_napalm_wrap 450def environment(**kwargs): # pylint: disable=unused-argument 451 """ 452 Returns the environment of the device. 453 454 CLI Example: 455 456 .. code-block:: bash 457 458 salt '*' net.environment 459 460 461 Example output: 462 463 .. code-block:: python 464 465 { 466 'fans': { 467 'Bottom Rear Fan': { 468 'status': True 469 }, 470 'Bottom Middle Fan': { 471 'status': True 472 }, 473 'Top Middle Fan': { 474 'status': True 475 }, 476 'Bottom Front Fan': { 477 'status': True 478 }, 479 'Top Front Fan': { 480 'status': True 481 }, 482 'Top Rear Fan': { 483 'status': True 484 } 485 }, 486 'memory': { 487 'available_ram': 16349, 488 'used_ram': 4934 489 }, 490 'temperature': { 491 'FPC 0 Exhaust A': { 492 'is_alert': False, 493 'temperature': 35.0, 494 'is_critical': False 495 } 496 }, 497 'cpu': { 498 '1': { 499 '%usage': 19.0 500 }, 501 '0': { 502 '%usage': 35.0 503 } 504 } 505 } 506 """ 507 508 return salt.utils.napalm.call( 509 napalm_device, "get_environment", **{} # pylint: disable=undefined-variable 510 ) 511 512 513@salt.utils.napalm.proxy_napalm_wrap 514def cli(*commands, **kwargs): # pylint: disable=unused-argument 515 """ 516 Returns a dictionary with the raw output of all commands passed as arguments. 517 518 commands 519 List of commands to be executed on the device. 520 521 textfsm_parse: ``False`` 522 Try parsing the outputs using the TextFSM templates. 523 524 .. versionadded:: 2018.3.0 525 526 .. note:: 527 This option can be also specified in the minion configuration 528 file or pillar as ``napalm_cli_textfsm_parse``. 529 530 textfsm_path 531 The path where the TextFSM templates can be found. This option implies 532 the usage of the TextFSM index file. 533 ``textfsm_path`` can be either absolute path on the server, 534 either specified using the following URL mschemes: ``file://``, 535 ``salt://``, ``http://``, ``https://``, ``ftp://``, 536 ``s3://``, ``swift://``. 537 538 .. versionadded:: 2018.3.0 539 540 .. note:: 541 This needs to be a directory with a flat structure, having an 542 index file (whose name can be specified using the ``index_file`` option) 543 and a number of TextFSM templates. 544 545 .. note:: 546 This option can be also specified in the minion configuration 547 file or pillar as ``textfsm_path``. 548 549 textfsm_template 550 The path to a certain the TextFSM template. 551 This can be specified using the absolute path 552 to the file, or using one of the following URL schemes: 553 554 - ``salt://``, to fetch the template from the Salt fileserver. 555 - ``http://`` or ``https://`` 556 - ``ftp://`` 557 - ``s3://`` 558 - ``swift://`` 559 560 .. versionadded:: 2018.3.0 561 562 textfsm_template_dict 563 A dictionary with the mapping between a command 564 and the corresponding TextFSM path to use to extract the data. 565 The TextFSM paths can be specified as in ``textfsm_template``. 566 567 .. versionadded:: 2018.3.0 568 569 .. note:: 570 This option can be also specified in the minion configuration 571 file or pillar as ``napalm_cli_textfsm_template_dict``. 572 573 platform_grain_name: ``os`` 574 The name of the grain used to identify the platform name 575 in the TextFSM index file. Default: ``os``. 576 577 .. versionadded:: 2018.3.0 578 579 .. note:: 580 This option can be also specified in the minion configuration 581 file or pillar as ``textfsm_platform_grain``. 582 583 platform_column_name: ``Platform`` 584 The column name used to identify the platform, 585 exactly as specified in the TextFSM index file. 586 Default: ``Platform``. 587 588 .. versionadded:: 2018.3.0 589 590 .. note:: 591 This is field is case sensitive, make sure 592 to assign the correct value to this option, 593 exactly as defined in the index file. 594 595 .. note:: 596 This option can be also specified in the minion configuration 597 file or pillar as ``textfsm_platform_column_name``. 598 599 index_file: ``index`` 600 The name of the TextFSM index file, under the ``textfsm_path``. Default: ``index``. 601 602 .. versionadded:: 2018.3.0 603 604 .. note:: 605 This option can be also specified in the minion configuration 606 file or pillar as ``textfsm_index_file``. 607 608 saltenv: ``base`` 609 Salt fileserver environment from which to retrieve the file. 610 Ignored if ``textfsm_path`` is not a ``salt://`` URL. 611 612 .. versionadded:: 2018.3.0 613 614 include_empty: ``False`` 615 Include empty files under the ``textfsm_path``. 616 617 .. versionadded:: 2018.3.0 618 619 include_pat 620 Glob or regex to narrow down the files cached from the given path. 621 If matching with a regex, the regex must be prefixed with ``E@``, 622 otherwise the expression will be interpreted as a glob. 623 624 .. versionadded:: 2018.3.0 625 626 exclude_pat 627 Glob or regex to exclude certain files from being cached from the given path. 628 If matching with a regex, the regex must be prefixed with ``E@``, 629 otherwise the expression will be interpreted as a glob. 630 631 .. versionadded:: 2018.3.0 632 633 .. note:: 634 If used with ``include_pat``, files matching this pattern will be 635 excluded from the subset of files defined by ``include_pat``. 636 637 CLI Example: 638 639 .. code-block:: bash 640 641 salt '*' net.cli "show version" "show chassis fan" 642 643 CLI Example with TextFSM template: 644 645 .. code-block:: bash 646 647 salt '*' net.cli textfsm_parse=True textfsm_path=salt://textfsm/ 648 649 Example output: 650 651 .. code-block:: python 652 653 { 654 'show version and haiku': 'Hostname: re0.edge01.arn01 655 Model: mx480 656 Junos: 13.3R6.5 657 Help me, Obi-Wan 658 I just saw Episode Two 659 You're my only hope 660 ', 661 'show chassis fan' : 'Item Status RPM Measurement 662 Top Rear Fan OK 3840 Spinning at intermediate-speed 663 Bottom Rear Fan OK 3840 Spinning at intermediate-speed 664 Top Middle Fan OK 3900 Spinning at intermediate-speed 665 Bottom Middle Fan OK 3840 Spinning at intermediate-speed 666 Top Front Fan OK 3810 Spinning at intermediate-speed 667 Bottom Front Fan OK 3840 Spinning at intermediate-speed 668 ' 669 } 670 671 Example output with TextFSM parsing: 672 673 .. code-block:: json 674 675 { 676 "comment": "", 677 "result": true, 678 "out": { 679 "sh ver": [ 680 { 681 "kernel": "9.1S3.5", 682 "documentation": "9.1S3.5", 683 "boot": "9.1S3.5", 684 "crypto": "9.1S3.5", 685 "chassis": "", 686 "routing": "9.1S3.5", 687 "base": "9.1S3.5", 688 "model": "mx960" 689 } 690 ] 691 } 692 } 693 """ 694 raw_cli_outputs = salt.utils.napalm.call( 695 napalm_device, # pylint: disable=undefined-variable 696 "cli", 697 **{"commands": list(commands)} 698 ) 699 # thus we can display the output as is 700 # in case of errors, they'll be caught in the proxy 701 if not raw_cli_outputs["result"]: 702 # Error -> dispaly the output as-is. 703 return raw_cli_outputs 704 textfsm_parse = ( 705 kwargs.get("textfsm_parse") 706 or __opts__.get("napalm_cli_textfsm_parse") 707 or __pillar__.get("napalm_cli_textfsm_parse", False) 708 ) 709 if not textfsm_parse: 710 # No TextFSM parsing required, return raw commands. 711 log.debug("No TextFSM parsing requested.") 712 return raw_cli_outputs 713 if "textfsm.extract" not in __salt__ or "textfsm.index" not in __salt__: 714 raw_cli_outputs["comment"] += "Unable to process: is TextFSM installed?" 715 log.error(raw_cli_outputs["comment"]) 716 return raw_cli_outputs 717 textfsm_template = kwargs.get("textfsm_template") 718 log.debug("textfsm_template: %s", textfsm_template) 719 textfsm_path = ( 720 kwargs.get("textfsm_path") 721 or __opts__.get("textfsm_path") 722 or __pillar__.get("textfsm_path") 723 ) 724 log.debug("textfsm_path: %s", textfsm_path) 725 textfsm_template_dict = ( 726 kwargs.get("textfsm_template_dict") 727 or __opts__.get("napalm_cli_textfsm_template_dict") 728 or __pillar__.get("napalm_cli_textfsm_template_dict", {}) 729 ) 730 log.debug("TextFSM command-template mapping: %s", textfsm_template_dict) 731 index_file = ( 732 kwargs.get("index_file") 733 or __opts__.get("textfsm_index_file") 734 or __pillar__.get("textfsm_index_file") 735 ) 736 log.debug("index_file: %s", index_file) 737 platform_grain_name = ( 738 kwargs.get("platform_grain_name") 739 or __opts__.get("textfsm_platform_grain") 740 or __pillar__.get("textfsm_platform_grain", "os") 741 ) 742 log.debug("platform_grain_name: %s", platform_grain_name) 743 platform_column_name = ( 744 kwargs.get("platform_column_name") 745 or __opts__.get("textfsm_platform_column_name") 746 or __pillar__.get("textfsm_platform_column_name", "Platform") 747 ) 748 log.debug("platform_column_name: %s", platform_column_name) 749 saltenv = kwargs.get("saltenv", "base") 750 include_empty = kwargs.get("include_empty", False) 751 include_pat = kwargs.get("include_pat") 752 exclude_pat = kwargs.get("exclude_pat") 753 processed_cli_outputs = { 754 "comment": raw_cli_outputs.get("comment", ""), 755 "result": raw_cli_outputs["result"], 756 "out": {}, 757 } 758 log.debug("Starting to analyse the raw outputs") 759 for command in list(commands): 760 command_output = raw_cli_outputs["out"][command] 761 log.debug("Output from command: %s", command) 762 log.debug(command_output) 763 processed_command_output = None 764 if textfsm_path: 765 log.debug("Using the templates under %s", textfsm_path) 766 processed_cli_output = __salt__["textfsm.index"]( 767 command, 768 platform_grain_name=platform_grain_name, 769 platform_column_name=platform_column_name, 770 output=command_output.strip(), 771 textfsm_path=textfsm_path, 772 saltenv=saltenv, 773 include_empty=include_empty, 774 include_pat=include_pat, 775 exclude_pat=exclude_pat, 776 ) 777 log.debug("Processed CLI output:") 778 log.debug(processed_cli_output) 779 if not processed_cli_output["result"]: 780 log.debug("Apparently this did not work, returning the raw output") 781 processed_command_output = command_output 782 processed_cli_outputs[ 783 "comment" 784 ] += "\nUnable to process the output from {}: {}.".format( 785 command, processed_cli_output["comment"] 786 ) 787 log.error(processed_cli_outputs["comment"]) 788 elif processed_cli_output["out"]: 789 log.debug("All good, %s has a nice output!", command) 790 processed_command_output = processed_cli_output["out"] 791 else: 792 comment = """\nProcessing "{}" didn't fail, but didn't return anything either. Dumping raw.""".format( 793 command 794 ) 795 processed_cli_outputs["comment"] += comment 796 log.error(comment) 797 processed_command_output = command_output 798 elif textfsm_template or command in textfsm_template_dict: 799 if command in textfsm_template_dict: 800 textfsm_template = textfsm_template_dict[command] 801 log.debug("Using %s to process the command: %s", textfsm_template, command) 802 processed_cli_output = __salt__["textfsm.extract"]( 803 textfsm_template, raw_text=command_output, saltenv=saltenv 804 ) 805 log.debug("Processed CLI output:") 806 log.debug(processed_cli_output) 807 if not processed_cli_output["result"]: 808 log.debug("Apparently this did not work, returning the raw output") 809 processed_command_output = command_output 810 processed_cli_outputs[ 811 "comment" 812 ] += "\nUnable to process the output from {}: {}".format( 813 command, processed_cli_output["comment"] 814 ) 815 log.error(processed_cli_outputs["comment"]) 816 elif processed_cli_output["out"]: 817 log.debug("All good, %s has a nice output!", command) 818 processed_command_output = processed_cli_output["out"] 819 else: 820 log.debug( 821 "Processing %s did not fail, but did not return" 822 " anything either. Dumping raw.", 823 command, 824 ) 825 processed_command_output = command_output 826 else: 827 log.error("No TextFSM template specified, or no TextFSM path defined") 828 processed_command_output = command_output 829 processed_cli_outputs[ 830 "comment" 831 ] += "\nUnable to process the output from {}.".format(command) 832 processed_cli_outputs["out"][command] = processed_command_output 833 processed_cli_outputs["comment"] = processed_cli_outputs["comment"].strip() 834 return processed_cli_outputs 835 836 837@salt.utils.napalm.proxy_napalm_wrap 838def traceroute( 839 destination, source=None, ttl=None, timeout=None, vrf=None, **kwargs 840): # pylint: disable=unused-argument 841 842 """ 843 Calls the method traceroute from the NAPALM driver object and returns a dictionary with the result of the traceroute 844 command executed on the device. 845 846 destination 847 Hostname or address of remote host 848 849 source 850 Source address to use in outgoing traceroute packets 851 852 ttl 853 IP maximum time-to-live value (or IPv6 maximum hop-limit value) 854 855 timeout 856 Number of seconds to wait for response (seconds) 857 858 vrf 859 VRF (routing instance) for traceroute attempt 860 861 .. versionadded:: 2016.11.4 862 863 CLI Example: 864 865 .. code-block:: bash 866 867 salt '*' net.traceroute 8.8.8.8 868 salt '*' net.traceroute 8.8.8.8 source=127.0.0.1 ttl=5 timeout=1 869 """ 870 871 return salt.utils.napalm.call( 872 napalm_device, # pylint: disable=undefined-variable 873 "traceroute", 874 **{ 875 "destination": destination, 876 "source": source, 877 "ttl": ttl, 878 "timeout": timeout, 879 "vrf": vrf, 880 } 881 ) 882 883 884@salt.utils.napalm.proxy_napalm_wrap 885def ping( 886 destination, 887 source=None, 888 ttl=None, 889 timeout=None, 890 size=None, 891 count=None, 892 vrf=None, 893 **kwargs 894): # pylint: disable=unused-argument 895 896 """ 897 Executes a ping on the network device and returns a dictionary as a result. 898 899 destination 900 Hostname or IP address of remote host 901 902 source 903 Source address of echo request 904 905 ttl 906 IP time-to-live value (IPv6 hop-limit value) (1..255 hops) 907 908 timeout 909 Maximum wait time after sending final packet (seconds) 910 911 size 912 Size of request packets (0..65468 bytes) 913 914 count 915 Number of ping requests to send (1..2000000000 packets) 916 917 vrf 918 VRF (routing instance) for ping attempt 919 920 .. versionadded:: 2016.11.4 921 922 CLI Example: 923 924 .. code-block:: bash 925 926 salt '*' net.ping 8.8.8.8 927 salt '*' net.ping 8.8.8.8 ttl=3 size=65468 928 salt '*' net.ping 8.8.8.8 source=127.0.0.1 timeout=1 count=100 929 """ 930 931 return salt.utils.napalm.call( 932 napalm_device, # pylint: disable=undefined-variable 933 "ping", 934 **{ 935 "destination": destination, 936 "source": source, 937 "ttl": ttl, 938 "timeout": timeout, 939 "size": size, 940 "count": count, 941 "vrf": vrf, 942 } 943 ) 944 945 946@salt.utils.napalm.proxy_napalm_wrap 947def arp( 948 interface="", ipaddr="", macaddr="", **kwargs 949): # pylint: disable=unused-argument 950 951 """ 952 NAPALM returns a list of dictionaries with details of the ARP entries. 953 954 :param interface: interface name to filter on 955 :param ipaddr: IP address to filter on 956 :param macaddr: MAC address to filter on 957 :return: List of the entries in the ARP table 958 959 CLI Example: 960 961 .. code-block:: bash 962 963 salt '*' net.arp 964 salt '*' net.arp macaddr='5c:5e:ab:da:3c:f0' 965 966 Example output: 967 968 .. code-block:: python 969 970 [ 971 { 972 'interface' : 'MgmtEth0/RSP0/CPU0/0', 973 'mac' : '5c:5e:ab:da:3c:f0', 974 'ip' : '172.17.17.1', 975 'age' : 1454496274.84 976 }, 977 { 978 'interface': 'MgmtEth0/RSP0/CPU0/0', 979 'mac' : '66:0e:94:96:e0:ff', 980 'ip' : '172.17.17.2', 981 'age' : 1435641582.49 982 } 983 ] 984 """ 985 986 proxy_output = salt.utils.napalm.call( 987 napalm_device, "get_arp_table", **{} # pylint: disable=undefined-variable 988 ) 989 990 if not proxy_output.get("result"): 991 return proxy_output 992 993 arp_table = proxy_output.get("out") 994 995 if interface: 996 arp_table = _filter_list(arp_table, "interface", interface) 997 998 if ipaddr: 999 arp_table = _filter_list(arp_table, "ip", ipaddr) 1000 1001 if macaddr: 1002 arp_table = _filter_list(arp_table, "mac", macaddr) 1003 1004 proxy_output.update({"out": arp_table}) 1005 1006 return proxy_output 1007 1008 1009@salt.utils.napalm.proxy_napalm_wrap 1010def ipaddrs(**kwargs): # pylint: disable=unused-argument 1011 1012 """ 1013 Returns IP addresses configured on the device. 1014 1015 :return: A dictionary with the IPv4 and IPv6 addresses of the interfaces. 1016 Returns all configured IP addresses on all interfaces as a dictionary 1017 of dictionaries. Keys of the main dictionary represent the name of the 1018 interface. Values of the main dictionary represent are dictionaries 1019 that may consist of two keys 'ipv4' and 'ipv6' (one, both or none) 1020 which are themselvs dictionaries with the IP addresses as keys. 1021 1022 CLI Example: 1023 1024 .. code-block:: bash 1025 1026 salt '*' net.ipaddrs 1027 1028 Example output: 1029 1030 .. code-block:: python 1031 1032 { 1033 'FastEthernet8': { 1034 'ipv4': { 1035 '10.66.43.169': { 1036 'prefix_length': 22 1037 } 1038 } 1039 }, 1040 'Loopback555': { 1041 'ipv4': { 1042 '192.168.1.1': { 1043 'prefix_length': 24 1044 } 1045 }, 1046 'ipv6': { 1047 '1::1': { 1048 'prefix_length': 64 1049 }, 1050 '2001:DB8:1::1': { 1051 'prefix_length': 64 1052 }, 1053 'FE80::3': { 1054 'prefix_length': 'N/A' 1055 } 1056 } 1057 } 1058 } 1059 """ 1060 1061 return salt.utils.napalm.call( 1062 napalm_device, "get_interfaces_ip", **{} # pylint: disable=undefined-variable 1063 ) 1064 1065 1066@salt.utils.napalm.proxy_napalm_wrap 1067def interfaces(**kwargs): # pylint: disable=unused-argument 1068 1069 """ 1070 Returns details of the interfaces on the device. 1071 1072 :return: Returns a dictionary of dictionaries. The keys for the first 1073 dictionary will be the interfaces in the devices. 1074 1075 CLI Example: 1076 1077 .. code-block:: bash 1078 1079 salt '*' net.interfaces 1080 1081 Example output: 1082 1083 .. code-block:: python 1084 1085 { 1086 'Management1': { 1087 'is_up': False, 1088 'is_enabled': False, 1089 'description': '', 1090 'last_flapped': -1, 1091 'speed': 1000, 1092 'mac_address': 'dead:beef:dead', 1093 }, 1094 'Ethernet1':{ 1095 'is_up': True, 1096 'is_enabled': True, 1097 'description': 'foo', 1098 'last_flapped': 1429978575.1554043, 1099 'speed': 1000, 1100 'mac_address': 'beef:dead:beef', 1101 } 1102 } 1103 """ 1104 1105 return salt.utils.napalm.call( 1106 napalm_device, "get_interfaces", **{} # pylint: disable=undefined-variable 1107 ) 1108 1109 1110@salt.utils.napalm.proxy_napalm_wrap 1111def lldp(interface="", **kwargs): # pylint: disable=unused-argument 1112 1113 """ 1114 Returns a detailed view of the LLDP neighbors. 1115 1116 :param interface: interface name to filter on 1117 1118 :return: A dictionary with the LLDL neighbors. The keys are the 1119 interfaces with LLDP activated on. 1120 1121 CLI Example: 1122 1123 .. code-block:: bash 1124 1125 salt '*' net.lldp 1126 salt '*' net.lldp interface='TenGigE0/0/0/8' 1127 1128 Example output: 1129 1130 .. code-block:: python 1131 1132 { 1133 'TenGigE0/0/0/8': [ 1134 { 1135 'parent_interface': 'Bundle-Ether8', 1136 'interface_description': 'TenGigE0/0/0/8', 1137 'remote_chassis_id': '8c60.4f69.e96c', 1138 'remote_system_name': 'switch', 1139 'remote_port': 'Eth2/2/1', 1140 'remote_port_description': 'Ethernet2/2/1', 1141 'remote_system_description': 'Cisco Nexus Operating System (NX-OS) Software 7.1(0)N1(1a) 1142 TAC support: http://www.cisco.com/tac 1143 Copyright (c) 2002-2015, Cisco Systems, Inc. All rights reserved.', 1144 'remote_system_capab': 'B, R', 1145 'remote_system_enable_capab': 'B' 1146 } 1147 ] 1148 } 1149 """ 1150 1151 proxy_output = salt.utils.napalm.call( 1152 napalm_device, # pylint: disable=undefined-variable 1153 "get_lldp_neighbors_detail", 1154 **{} 1155 ) 1156 1157 if not proxy_output.get("result"): 1158 return proxy_output 1159 1160 lldp_neighbors = proxy_output.get("out") 1161 1162 if interface: 1163 lldp_neighbors = {interface: lldp_neighbors.get(interface)} 1164 1165 proxy_output.update({"out": lldp_neighbors}) 1166 1167 return proxy_output 1168 1169 1170@salt.utils.napalm.proxy_napalm_wrap 1171def mac(address="", interface="", vlan=0, **kwargs): # pylint: disable=unused-argument 1172 1173 """ 1174 Returns the MAC Address Table on the device. 1175 1176 :param address: MAC address to filter on 1177 :param interface: Interface name to filter on 1178 :param vlan: VLAN identifier 1179 :return: A list of dictionaries representing the entries in the MAC Address Table 1180 1181 CLI Example: 1182 1183 .. code-block:: bash 1184 1185 salt '*' net.mac 1186 salt '*' net.mac vlan=10 1187 1188 Example output: 1189 1190 .. code-block:: python 1191 1192 [ 1193 { 1194 'mac' : '00:1c:58:29:4a:71', 1195 'interface' : 'xe-3/0/2', 1196 'static' : False, 1197 'active' : True, 1198 'moves' : 1, 1199 'vlan' : 10, 1200 'last_move' : 1454417742.58 1201 }, 1202 { 1203 'mac' : '8c:60:4f:58:e1:c1', 1204 'interface' : 'xe-1/0/1', 1205 'static' : False, 1206 'active' : True, 1207 'moves' : 2, 1208 'vlan' : 42, 1209 'last_move' : 1453191948.11 1210 } 1211 ] 1212 """ 1213 1214 proxy_output = salt.utils.napalm.call( 1215 napalm_device, # pylint: disable=undefined-variable 1216 "get_mac_address_table", 1217 **{} 1218 ) 1219 1220 if not proxy_output.get("result"): 1221 # if negative, leave the output unchanged 1222 return proxy_output 1223 1224 mac_address_table = proxy_output.get("out") 1225 1226 if vlan and isinstance(vlan, int): 1227 mac_address_table = _filter_list(mac_address_table, "vlan", vlan) 1228 1229 if address: 1230 mac_address_table = _filter_list(mac_address_table, "mac", address) 1231 1232 if interface: 1233 mac_address_table = _filter_list(mac_address_table, "interface", interface) 1234 1235 proxy_output.update({"out": mac_address_table}) 1236 1237 return proxy_output 1238 1239 1240@salt.utils.napalm.proxy_napalm_wrap 1241def config(source=None, **kwargs): # pylint: disable=unused-argument 1242 """ 1243 .. versionadded:: 2017.7.0 1244 1245 Return the whole configuration of the network device. By default, it will 1246 return all possible configuration sources supported by the network device. 1247 At most, there will be: 1248 1249 - running config 1250 - startup config 1251 - candidate config 1252 1253 To return only one of the configurations, you can use the ``source`` 1254 argument. 1255 1256 source 1257 Which configuration type you want to display, default is all of them. 1258 1259 Options: 1260 1261 - running 1262 - candidate 1263 - startup 1264 1265 :return: 1266 The object returned is a dictionary with the following keys: 1267 1268 - running (string): Representation of the native running configuration. 1269 - candidate (string): Representation of the native candidate configuration. 1270 If the device doesn't differentiate between running and startup 1271 configuration this will an empty string. 1272 - startup (string): Representation of the native startup configuration. 1273 If the device doesn't differentiate between running and startup 1274 configuration this will an empty string. 1275 1276 CLI Example: 1277 1278 .. code-block:: bash 1279 1280 salt '*' net.config 1281 salt '*' net.config source=candidate 1282 """ 1283 return salt.utils.napalm.call( 1284 napalm_device, # pylint: disable=undefined-variable 1285 "get_config", 1286 **{"retrieve": source} 1287 ) 1288 1289 1290@salt.utils.napalm.proxy_napalm_wrap 1291def optics(**kwargs): # pylint: disable=unused-argument 1292 """ 1293 .. versionadded:: 2017.7.0 1294 1295 Fetches the power usage on the various transceivers installed 1296 on the network device (in dBm), and returns a view that conforms with the 1297 OpenConfig model openconfig-platform-transceiver.yang. 1298 1299 :return: 1300 Returns a dictionary where the keys are as listed below: 1301 * intf_name (unicode) 1302 * physical_channels 1303 * channels (list of dicts) 1304 * index (int) 1305 * state 1306 * input_power 1307 * instant (float) 1308 * avg (float) 1309 * min (float) 1310 * max (float) 1311 * output_power 1312 * instant (float) 1313 * avg (float) 1314 * min (float) 1315 * max (float) 1316 * laser_bias_current 1317 * instant (float) 1318 * avg (float) 1319 * min (float) 1320 * max (float) 1321 1322 CLI Example: 1323 1324 .. code-block:: bash 1325 1326 salt '*' net.optics 1327 """ 1328 return salt.utils.napalm.call( 1329 napalm_device, "get_optics", **{} # pylint: disable=undefined-variable 1330 ) 1331 1332 1333# <---- Call NAPALM getters -------------------------------------------------------------------------------------------- 1334 1335# ----- Configuration specific functions ------------------------------------------------------------------------------> 1336 1337 1338@salt.utils.napalm.proxy_napalm_wrap 1339def load_config( 1340 filename=None, 1341 text=None, 1342 test=False, 1343 commit=True, 1344 debug=False, 1345 replace=False, 1346 commit_in=None, 1347 commit_at=None, 1348 revert_in=None, 1349 revert_at=None, 1350 commit_jid=None, 1351 inherit_napalm_device=None, 1352 saltenv="base", 1353 **kwargs 1354): # pylint: disable=unused-argument 1355 """ 1356 Applies configuration changes on the device. It can be loaded from a file or from inline string. 1357 If you send both a filename and a string containing the configuration, the file has higher precedence. 1358 1359 By default this function will commit the changes. If there are no changes, it does not commit and 1360 the flag ``already_configured`` will be set as ``True`` to point this out. 1361 1362 To avoid committing the configuration, set the argument ``test`` to ``True`` and will discard (dry run). 1363 1364 To keep the changes but not commit, set ``commit`` to ``False``. 1365 1366 To replace the config, set ``replace`` to ``True``. 1367 1368 filename 1369 Path to the file containing the desired configuration. 1370 This can be specified using the absolute path to the file, 1371 or using one of the following URL schemes: 1372 1373 - ``salt://``, to fetch the template from the Salt fileserver. 1374 - ``http://`` or ``https://`` 1375 - ``ftp://`` 1376 - ``s3://`` 1377 - ``swift://`` 1378 1379 .. versionchanged:: 2018.3.0 1380 1381 text 1382 String containing the desired configuration. 1383 This argument is ignored when ``filename`` is specified. 1384 1385 test: False 1386 Dry run? If set as ``True``, will apply the config, discard and return the changes. Default: ``False`` 1387 and will commit the changes on the device. 1388 1389 commit: True 1390 Commit? Default: ``True``. 1391 1392 debug: False 1393 Debug mode. Will insert a new key under the output dictionary, as ``loaded_config`` containing the raw 1394 configuration loaded on the device. 1395 1396 .. versionadded:: 2016.11.2 1397 1398 replace: False 1399 Load and replace the configuration. Default: ``False``. 1400 1401 .. versionadded:: 2016.11.2 1402 1403 commit_in: ``None`` 1404 Commit the changes in a specific number of minutes / hours. Example of 1405 accepted formats: ``5`` (commit in 5 minutes), ``2m`` (commit in 2 1406 minutes), ``1h`` (commit the changes in 1 hour)`, ``5h30m`` (commit 1407 the changes in 5 hours and 30 minutes). 1408 1409 .. note:: 1410 This feature works on any platforms, as it does not rely on the 1411 native features of the network operating system. 1412 1413 .. note:: 1414 After the command is executed and the ``diff`` is not satisfactory, 1415 or for any other reasons you have to discard the commit, you are 1416 able to do so using the 1417 :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>` 1418 execution function, using the commit ID returned by this function. 1419 1420 .. warning:: 1421 Using this feature, Salt will load the exact configuration you 1422 expect, however the diff may change in time (i.e., if an user 1423 applies a manual configuration change, or a different process or 1424 command changes the configuration in the meanwhile). 1425 1426 .. versionadded:: 2019.2.0 1427 1428 commit_at: ``None`` 1429 Commit the changes at a specific time. Example of accepted formats: 1430 ``1am`` (will commit the changes at the next 1AM), ``13:20`` (will 1431 commit at 13:20), ``1:20am``, etc. 1432 1433 .. note:: 1434 This feature works on any platforms, as it does not rely on the 1435 native features of the network operating system. 1436 1437 .. note:: 1438 After the command is executed and the ``diff`` is not satisfactory, 1439 or for any other reasons you have to discard the commit, you are 1440 able to do so using the 1441 :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>` 1442 execution function, using the commit ID returned by this function. 1443 1444 .. warning:: 1445 Using this feature, Salt will load the exact configuration you 1446 expect, however the diff may change in time (i.e., if an user 1447 applies a manual configuration change, or a different process or 1448 command changes the configuration in the meanwhile). 1449 1450 .. versionadded:: 2019.2.0 1451 1452 revert_in: ``None`` 1453 Commit and revert the changes in a specific number of minutes / hours. 1454 Example of accepted formats: ``5`` (revert in 5 minutes), ``2m`` (revert 1455 in 2 minutes), ``1h`` (revert the changes in 1 hour)`, ``5h30m`` (revert 1456 the changes in 5 hours and 30 minutes). 1457 1458 .. note:: 1459 To confirm the commit, and prevent reverting the changes, you will 1460 have to execute the 1461 :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>` 1462 function, using the commit ID returned by this function. 1463 1464 .. warning:: 1465 This works on any platform, regardless if they have or don't have 1466 native capabilities to confirming a commit. However, please be 1467 *very* cautious when using this feature: on Junos (as it is the only 1468 NAPALM core platform supporting this natively) it executes a commit 1469 confirmed as you would do from the command line. 1470 All the other platforms don't have this capability natively, 1471 therefore the revert is done via Salt. That means, your device needs 1472 to be reachable at the moment when Salt will attempt to revert your 1473 changes. Be cautious when pushing configuration changes that would 1474 prevent you reach the device. 1475 1476 Similarly, if an user or a different process apply other 1477 configuration changes in the meanwhile (between the moment you 1478 commit and till the changes are reverted), these changes would be 1479 equally reverted, as Salt cannot be aware of them. 1480 1481 .. versionadded:: 2019.2.0 1482 1483 revert_at: ``None`` 1484 Commit and revert the changes at a specific time. Example of accepted 1485 formats: ``1am`` (will commit and revert the changes at the next 1AM), 1486 ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc. 1487 1488 .. note:: 1489 To confirm the commit, and prevent reverting the changes, you will 1490 have to execute the 1491 :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>` 1492 function, using the commit ID returned by this function. 1493 1494 .. warning:: 1495 This works on any platform, regardless if they have or don't have 1496 native capabilities to confirming a commit. However, please be 1497 *very* cautious when using this feature: on Junos (as it is the only 1498 NAPALM core platform supporting this natively) it executes a commit 1499 confirmed as you would do from the command line. 1500 All the other platforms don't have this capability natively, 1501 therefore the revert is done via Salt. That means, your device needs 1502 to be reachable at the moment when Salt will attempt to revert your 1503 changes. Be cautious when pushing configuration changes that would 1504 prevent you reach the device. 1505 1506 Similarly, if an user or a different process apply other 1507 configuration changes in the meanwhile (between the moment you 1508 commit and till the changes are reverted), these changes would be 1509 equally reverted, as Salt cannot be aware of them. 1510 1511 .. versionadded:: 2019.2.0 1512 1513 saltenv: ``base`` 1514 Specifies the Salt environment name. 1515 1516 .. versionadded:: 2018.3.0 1517 1518 :return: a dictionary having the following keys: 1519 1520 * result (bool): if the config was applied successfully. It is ``False`` only in case of failure. In case \ 1521 there are no changes to be applied and successfully performs all operations it is still ``True`` and so will be \ 1522 the ``already_configured`` flag (example below) 1523 * comment (str): a message for the user 1524 * already_configured (bool): flag to check if there were no changes applied 1525 * loaded_config (str): the configuration loaded on the device. Requires ``debug`` to be set as ``True`` 1526 * diff (str): returns the config changes applied 1527 1528 CLI Example: 1529 1530 .. code-block:: bash 1531 1532 salt '*' net.load_config text='ntp peer 192.168.0.1' 1533 salt '*' net.load_config filename='/absolute/path/to/your/file' 1534 salt '*' net.load_config filename='/absolute/path/to/your/file' test=True 1535 salt '*' net.load_config filename='/absolute/path/to/your/file' commit=False 1536 1537 Example output: 1538 1539 .. code-block:: python 1540 1541 { 1542 'comment': 'Configuration discarded.', 1543 'already_configured': False, 1544 'result': True, 1545 'diff': '[edit interfaces xe-0/0/5]+ description "Adding a description";' 1546 } 1547 """ 1548 fun = "load_merge_candidate" 1549 if replace: 1550 fun = "load_replace_candidate" 1551 if salt.utils.napalm.not_always_alive(__opts__): 1552 # if a not-always-alive proxy 1553 # or regular minion 1554 # do not close the connection after loading the config 1555 # this will be handled in _config_logic 1556 # after running the other features: 1557 # compare_config, discard / commit 1558 # which have to be over the same session 1559 napalm_device["CLOSE"] = False # pylint: disable=undefined-variable 1560 if filename: 1561 text = __salt__["cp.get_file_str"](filename, saltenv=saltenv) 1562 if text is False: 1563 # When using salt:// or https://, if the resource is not available, 1564 # it will either raise an exception, or return False. 1565 ret = {"result": False, "out": None} 1566 ret[ 1567 "comment" 1568 ] = "Unable to read from {}. Please specify a valid file or text.".format( 1569 filename 1570 ) 1571 log.error(ret["comment"]) 1572 return ret 1573 if commit_jid: 1574 # When the commit_jid argument is passed, it probably is a scheduled 1575 # commit to be executed, and filename is a temporary file which 1576 # can be removed after reading it. 1577 salt.utils.files.safe_rm(filename) 1578 _loaded = salt.utils.napalm.call( 1579 napalm_device, fun, **{"config": text} # pylint: disable=undefined-variable 1580 ) 1581 return _config_logic( 1582 napalm_device, # pylint: disable=undefined-variable 1583 _loaded, 1584 test=test, 1585 debug=debug, 1586 replace=replace, 1587 commit_config=commit, 1588 loaded_config=text, 1589 commit_at=commit_at, 1590 commit_in=commit_in, 1591 revert_in=revert_in, 1592 revert_at=revert_at, 1593 commit_jid=commit_jid, 1594 **kwargs 1595 ) 1596 1597 1598@salt.utils.napalm.proxy_napalm_wrap 1599def load_template( 1600 template_name=None, 1601 template_source=None, 1602 context=None, 1603 defaults=None, 1604 template_engine="jinja", 1605 saltenv="base", 1606 template_hash=None, 1607 template_hash_name=None, 1608 skip_verify=False, 1609 test=False, 1610 commit=True, 1611 debug=False, 1612 replace=False, 1613 commit_in=None, 1614 commit_at=None, 1615 revert_in=None, 1616 revert_at=None, 1617 inherit_napalm_device=None, # pylint: disable=unused-argument 1618 **template_vars 1619): 1620 """ 1621 Renders a configuration template (default: Jinja) and loads the result on the device. 1622 1623 By default this function will commit the changes. If there are no changes, 1624 it does not commit, discards he config and the flag ``already_configured`` 1625 will be set as ``True`` to point this out. 1626 1627 To avoid committing the configuration, set the argument ``test`` to ``True`` 1628 and will discard (dry run). 1629 1630 To preserve the changes, set ``commit`` to ``False``. 1631 However, this is recommended to be used only in exceptional cases 1632 when there are applied few consecutive states 1633 and/or configuration changes. 1634 Otherwise the user might forget that the config DB is locked 1635 and the candidate config buffer is not cleared/merged in the running config. 1636 1637 To replace the config, set ``replace`` to ``True``. 1638 1639 template_name 1640 Identifies path to the template source. 1641 The template can be either stored on the local machine, either remotely. 1642 The recommended location is under the ``file_roots`` 1643 as specified in the master config file. 1644 For example, let's suppose the ``file_roots`` is configured as: 1645 1646 .. code-block:: yaml 1647 1648 file_roots: 1649 base: 1650 - /etc/salt/states 1651 1652 Placing the template under ``/etc/salt/states/templates/example.jinja``, 1653 it can be used as ``salt://templates/example.jinja``. 1654 Alternatively, for local files, the user can specify the absolute path. 1655 If remotely, the source can be retrieved via ``http``, ``https`` or ``ftp``. 1656 1657 Examples: 1658 1659 - ``salt://my_template.jinja`` 1660 - ``/absolute/path/to/my_template.jinja`` 1661 - ``http://example.com/template.cheetah`` 1662 - ``https:/example.com/template.mako`` 1663 - ``ftp://example.com/template.py`` 1664 1665 .. versionchanged:: 2019.2.0 1666 This argument can now support a list of templates to be rendered. 1667 The resulting configuration text is loaded at once, as a single 1668 configuration chunk. 1669 1670 template_source: None 1671 Inline config template to be rendered and loaded on the device. 1672 1673 template_hash: None 1674 Hash of the template file. Format: ``{hash_type: 'md5', 'hsum': <md5sum>}`` 1675 1676 .. versionadded:: 2016.11.2 1677 1678 context: None 1679 Overrides default context variables passed to the template. 1680 1681 .. versionadded:: 2019.2.0 1682 1683 template_hash_name: None 1684 When ``template_hash`` refers to a remote file, 1685 this specifies the filename to look for in that file. 1686 1687 .. versionadded:: 2016.11.2 1688 1689 saltenv: ``base`` 1690 Specifies the template environment. 1691 This will influence the relative imports inside the templates. 1692 1693 .. versionadded:: 2016.11.2 1694 1695 template_engine: jinja 1696 The following templates engines are supported: 1697 1698 - :mod:`cheetah<salt.renderers.cheetah>` 1699 - :mod:`genshi<salt.renderers.genshi>` 1700 - :mod:`jinja<salt.renderers.jinja>` 1701 - :mod:`mako<salt.renderers.mako>` 1702 - :mod:`py<salt.renderers.py>` 1703 - :mod:`wempy<salt.renderers.wempy>` 1704 1705 .. versionadded:: 2016.11.2 1706 1707 skip_verify: True 1708 If ``True``, hash verification of remote file sources 1709 (``http://``, ``https://``, ``ftp://``) will be skipped, 1710 and the ``source_hash`` argument will be ignored. 1711 1712 .. versionadded:: 2016.11.2 1713 1714 test: False 1715 Dry run? If set to ``True``, will apply the config, 1716 discard and return the changes. 1717 Default: ``False`` and will commit the changes on the device. 1718 1719 commit: True 1720 Commit? (default: ``True``) 1721 1722 debug: False 1723 Debug mode. Will insert a new key under the output dictionary, 1724 as ``loaded_config`` containing the raw result after the template was rendered. 1725 1726 .. versionadded:: 2016.11.2 1727 1728 replace: False 1729 Load and replace the configuration. 1730 1731 .. versionadded:: 2016.11.2 1732 1733 commit_in: ``None`` 1734 Commit the changes in a specific number of minutes / hours. Example of 1735 accepted formats: ``5`` (commit in 5 minutes), ``2m`` (commit in 2 1736 minutes), ``1h`` (commit the changes in 1 hour)`, ``5h30m`` (commit 1737 the changes in 5 hours and 30 minutes). 1738 1739 .. note:: 1740 This feature works on any platforms, as it does not rely on the 1741 native features of the network operating system. 1742 1743 .. note:: 1744 After the command is executed and the ``diff`` is not satisfactory, 1745 or for any other reasons you have to discard the commit, you are 1746 able to do so using the 1747 :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>` 1748 execution function, using the commit ID returned by this function. 1749 1750 .. warning:: 1751 Using this feature, Salt will load the exact configuration you 1752 expect, however the diff may change in time (i.e., if an user 1753 applies a manual configuration change, or a different process or 1754 command changes the configuration in the meanwhile). 1755 1756 .. versionadded:: 2019.2.0 1757 1758 commit_at: ``None`` 1759 Commit the changes at a specific time. Example of accepted formats: 1760 ``1am`` (will commit the changes at the next 1AM), ``13:20`` (will 1761 commit at 13:20), ``1:20am``, etc. 1762 1763 .. note:: 1764 This feature works on any platforms, as it does not rely on the 1765 native features of the network operating system. 1766 1767 .. note:: 1768 After the command is executed and the ``diff`` is not satisfactory, 1769 or for any other reasons you have to discard the commit, you are 1770 able to do so using the 1771 :py:func:`net.cancel_commit <salt.modules.napalm_network.cancel_commit>` 1772 execution function, using the commit ID returned by this function. 1773 1774 .. warning:: 1775 Using this feature, Salt will load the exact configuration you 1776 expect, however the diff may change in time (i.e., if an user 1777 applies a manual configuration change, or a different process or 1778 command changes the configuration in the meanwhile). 1779 1780 .. versionadded:: 2019.2.0 1781 1782 revert_in: ``None`` 1783 Commit and revert the changes in a specific number of minutes / hours. 1784 Example of accepted formats: ``5`` (revert in 5 minutes), ``2m`` (revert 1785 in 2 minutes), ``1h`` (revert the changes in 1 hour)`, ``5h30m`` (revert 1786 the changes in 5 hours and 30 minutes). 1787 1788 .. note:: 1789 To confirm the commit, and prevent reverting the changes, you will 1790 have to execute the 1791 :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>` 1792 function, using the commit ID returned by this function. 1793 1794 .. warning:: 1795 This works on any platform, regardless if they have or don't have 1796 native capabilities to confirming a commit. However, please be 1797 *very* cautious when using this feature: on Junos (as it is the only 1798 NAPALM core platform supporting this natively) it executes a commit 1799 confirmed as you would do from the command line. 1800 All the other platforms don't have this capability natively, 1801 therefore the revert is done via Salt. That means, your device needs 1802 to be reachable at the moment when Salt will attempt to revert your 1803 changes. Be cautious when pushing configuration changes that would 1804 prevent you reach the device. 1805 1806 Similarly, if an user or a different process apply other 1807 configuration changes in the meanwhile (between the moment you 1808 commit and till the changes are reverted), these changes would be 1809 equally reverted, as Salt cannot be aware of them. 1810 1811 .. versionadded:: 2019.2.0 1812 1813 revert_at: ``None`` 1814 Commit and revert the changes at a specific time. Example of accepted 1815 formats: ``1am`` (will commit and revert the changes at the next 1AM), 1816 ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc. 1817 1818 .. note:: 1819 To confirm the commit, and prevent reverting the changes, you will 1820 have to execute the 1821 :mod:`net.confirm_commit <salt.modules.napalm_network.confirm_commit>` 1822 function, using the commit ID returned by this function. 1823 1824 .. warning:: 1825 This works on any platform, regardless if they have or don't have 1826 native capabilities to confirming a commit. However, please be 1827 *very* cautious when using this feature: on Junos (as it is the only 1828 NAPALM core platform supporting this natively) it executes a commit 1829 confirmed as you would do from the command line. 1830 All the other platforms don't have this capability natively, 1831 therefore the revert is done via Salt. That means, your device needs 1832 to be reachable at the moment when Salt will attempt to revert your 1833 changes. Be cautious when pushing configuration changes that would 1834 prevent you reach the device. 1835 1836 Similarly, if an user or a different process apply other 1837 configuration changes in the meanwhile (between the moment you 1838 commit and till the changes are reverted), these changes would be 1839 equally reverted, as Salt cannot be aware of them. 1840 1841 .. versionadded:: 2019.2.0 1842 1843 defaults: None 1844 Default variables/context passed to the template. 1845 1846 .. versionadded:: 2016.11.2 1847 1848 template_vars 1849 Dictionary with the arguments/context to be used when the template is rendered. 1850 1851 .. note:: 1852 Do not explicitly specify this argument. This represents any other 1853 variable that will be sent to the template rendering system. 1854 Please see the examples below! 1855 1856 .. note:: 1857 It is more recommended to use the ``context`` argument to avoid 1858 conflicts between CLI arguments and template variables. 1859 1860 :return: a dictionary having the following keys: 1861 1862 - result (bool): if the config was applied successfully. It is ``False`` 1863 only in case of failure. In case there are no changes to be applied and 1864 successfully performs all operations it is still ``True`` and so will be 1865 the ``already_configured`` flag (example below) 1866 - comment (str): a message for the user 1867 - already_configured (bool): flag to check if there were no changes applied 1868 - loaded_config (str): the configuration loaded on the device, after 1869 rendering the template. Requires ``debug`` to be set as ``True`` 1870 - diff (str): returns the config changes applied 1871 1872 The template can use variables from the ``grains``, ``pillar`` or ``opts``, for example: 1873 1874 .. code-block:: jinja 1875 1876 {% set router_model = grains.get('model') -%} 1877 {% set router_vendor = grains.get('vendor') -%} 1878 {% set os_version = grains.get('version') -%} 1879 {% set hostname = pillar.get('proxy', {}).get('host') -%} 1880 {% if router_vendor|lower == 'juniper' %} 1881 system { 1882 host-name {{hostname}}; 1883 } 1884 {% elif router_vendor|lower == 'cisco' %} 1885 hostname {{hostname}} 1886 {% endif %} 1887 1888 CLI Examples: 1889 1890 .. code-block:: bash 1891 1892 salt '*' net.load_template set_ntp_peers peers=[192.168.0.1] # uses NAPALM default templates 1893 1894 # inline template: 1895 salt -G 'os:junos' net.load_template template_source='system { host-name {{host_name}}; }' \ 1896 host_name='MX480.lab' 1897 1898 # inline template using grains info: 1899 salt -G 'os:junos' net.load_template \ 1900 template_source='system { host-name {{grains.model}}.lab; }' 1901 # if the device is a MX480, the command above will set the hostname as: MX480.lab 1902 1903 # inline template using pillar data: 1904 salt -G 'os:junos' net.load_template template_source='system { host-name {{pillar.proxy.host}}; }' 1905 1906 salt '*' net.load_template https://bit.ly/2OhSgqP hostname=example # will commit 1907 salt '*' net.load_template https://bit.ly/2OhSgqP hostname=example test=True # dry run 1908 1909 salt '*' net.load_template salt://templates/example.jinja debug=True # Using the salt:// URI 1910 1911 # render a mako template: 1912 salt '*' net.load_template salt://templates/example.mako template_engine=mako debug=True 1913 1914 # render remote template 1915 salt -G 'os:junos' net.load_template http://bit.ly/2fReJg7 test=True debug=True peers=['192.168.0.1'] 1916 salt -G 'os:ios' net.load_template http://bit.ly/2gKOj20 test=True debug=True peers=['192.168.0.1'] 1917 1918 # render multiple templates at once 1919 salt '*' net.load_template "['https://bit.ly/2OhSgqP', 'salt://templates/example.jinja']" context="{'hostname': 'example'}" 1920 1921 Example output: 1922 1923 .. code-block:: python 1924 1925 { 1926 'comment': '', 1927 'already_configured': False, 1928 'result': True, 1929 'diff': '[edit system]+ host-name edge01.bjm01', 1930 'loaded_config': 'system { host-name edge01.bjm01; }'' 1931 } 1932 """ 1933 _rendered = "" 1934 _loaded = {"result": True, "comment": "", "out": None} 1935 loaded_config = None 1936 # prechecks 1937 if template_engine not in salt.utils.templates.TEMPLATE_REGISTRY: 1938 _loaded.update( 1939 { 1940 "result": False, 1941 "comment": ( 1942 "Invalid templating engine! Choose between: {tpl_eng_opts}".format( 1943 tpl_eng_opts=", ".join( 1944 list(salt.utils.templates.TEMPLATE_REGISTRY.keys()) 1945 ) 1946 ) 1947 ), 1948 } 1949 ) 1950 return _loaded # exit 1951 1952 # to check if will be rendered by salt or NAPALM 1953 salt_render_prefixes = ("salt://", "http://", "https://", "ftp://") 1954 salt_render = False 1955 file_exists = False 1956 if not isinstance(template_name, (tuple, list)): 1957 for salt_render_prefix in salt_render_prefixes: 1958 if not salt_render: 1959 salt_render = salt_render or template_name.startswith( 1960 salt_render_prefix 1961 ) 1962 file_exists = __salt__["file.file_exists"](template_name) 1963 1964 if context is None: 1965 context = {} 1966 context.update(template_vars) 1967 # if needed to render the template send as inline arg 1968 if template_source: 1969 # render the content 1970 _rendered = __salt__["file.apply_template_on_contents"]( 1971 contents=template_source, 1972 template=template_engine, 1973 context=context, 1974 defaults=defaults, 1975 saltenv=saltenv, 1976 ) 1977 if not isinstance(_rendered, str): 1978 if "result" in _rendered: 1979 _loaded["result"] = _rendered["result"] 1980 else: 1981 _loaded["result"] = False 1982 if "comment" in _rendered: 1983 _loaded["comment"] = _rendered["comment"] 1984 else: 1985 _loaded["comment"] = "Error while rendering the template." 1986 return _loaded 1987 else: 1988 # render the file - either local, either remote 1989 if not isinstance(template_name, (list, tuple)): 1990 template_name = [template_name] 1991 if template_hash_name and not isinstance(template_hash_name, (list, tuple)): 1992 template_hash_name = [template_hash_name] 1993 elif not template_hash_name: 1994 template_hash_name = [None] * len(template_name) 1995 if ( 1996 template_hash 1997 and isinstance(template_hash, str) 1998 and not ( 1999 template_hash.startswith("salt://") 2000 or template_hash.startswith("file://") 2001 ) 2002 ): 2003 # If the template hash is passed as string, and it's not a file 2004 # (starts with the salt:// or file:// URI), then make it a list 2005 # of 1 element (for the iteration below) 2006 template_hash = [template_hash] 2007 elif ( 2008 template_hash 2009 and isinstance(template_hash, str) 2010 and ( 2011 template_hash.startswith("salt://") 2012 or template_hash.startswith("file://") 2013 ) 2014 ): 2015 # If the template hash is a file URI, then provide the same value 2016 # for each of the templates in the list, as probably they all 2017 # share the same hash file, otherwise the user should provide 2018 # this as a list 2019 template_hash = [template_hash] * len(template_name) 2020 elif not template_hash: 2021 template_hash = [None] * len(template_name) 2022 for tpl_index, tpl_name in enumerate(template_name): 2023 tpl_hash = template_hash[tpl_index] 2024 tpl_hash_name = template_hash_name[tpl_index] 2025 _rand_filename = __salt__["random.hash"](tpl_name, "md5") 2026 _temp_file = __salt__["file.join"]("/tmp", _rand_filename) 2027 _managed = __salt__["file.get_managed"]( 2028 name=_temp_file, 2029 source=tpl_name, 2030 source_hash=tpl_hash, 2031 source_hash_name=tpl_hash_name, 2032 user=None, 2033 group=None, 2034 mode=None, 2035 attrs=None, 2036 template=template_engine, 2037 context=context, 2038 defaults=defaults, 2039 saltenv=saltenv, 2040 skip_verify=skip_verify, 2041 ) 2042 if not isinstance(_managed, (list, tuple)) and isinstance(_managed, str): 2043 _loaded["comment"] += _managed 2044 _loaded["result"] = False 2045 elif isinstance(_managed, (list, tuple)) and not len(_managed) > 0: 2046 _loaded["result"] = False 2047 _loaded["comment"] += "Error while rendering the template." 2048 elif isinstance(_managed, (list, tuple)) and not len(_managed[0]) > 0: 2049 _loaded["result"] = False 2050 _loaded["comment"] += _managed[-1] # contains the error message 2051 if _loaded["result"]: # all good 2052 _temp_tpl_file = _managed[0] 2053 _temp_tpl_file_exists = __salt__["file.file_exists"](_temp_tpl_file) 2054 if not _temp_tpl_file_exists: 2055 _loaded["result"] = False 2056 _loaded["comment"] += "Error while rendering the template." 2057 return _loaded 2058 _rendered += __salt__["file.read"](_temp_tpl_file) 2059 __salt__["file.remove"](_temp_tpl_file) 2060 else: 2061 return _loaded # exit 2062 2063 loaded_config = _rendered 2064 if _loaded["result"]: # all good 2065 fun = "load_merge_candidate" 2066 if replace: # replace requested 2067 fun = "load_replace_candidate" 2068 if salt.utils.napalm.not_always_alive(__opts__): 2069 # if a not-always-alive proxy 2070 # or regular minion 2071 # do not close the connection after loading the config 2072 # this will be handled in _config_logic 2073 # after running the other features: 2074 # compare_config, discard / commit 2075 # which have to be over the same session 2076 napalm_device["CLOSE"] = False # pylint: disable=undefined-variable 2077 _loaded = salt.utils.napalm.call( 2078 napalm_device, # pylint: disable=undefined-variable 2079 fun, 2080 **{"config": _rendered} 2081 ) 2082 return _config_logic( 2083 napalm_device, # pylint: disable=undefined-variable 2084 _loaded, 2085 test=test, 2086 debug=debug, 2087 replace=replace, 2088 commit_config=commit, 2089 loaded_config=loaded_config, 2090 commit_at=commit_at, 2091 commit_in=commit_in, 2092 revert_in=revert_in, 2093 revert_at=revert_at, 2094 **template_vars 2095 ) 2096 2097 2098@salt.utils.napalm.proxy_napalm_wrap 2099def commit(inherit_napalm_device=None, **kwargs): # pylint: disable=unused-argument 2100 2101 """ 2102 Commits the configuration changes made on the network device. 2103 2104 CLI Example: 2105 2106 .. code-block:: bash 2107 2108 salt '*' net.commit 2109 """ 2110 2111 return salt.utils.napalm.call( 2112 napalm_device, "commit_config", **{} # pylint: disable=undefined-variable 2113 ) 2114 2115 2116@salt.utils.napalm.proxy_napalm_wrap 2117def discard_config( 2118 inherit_napalm_device=None, **kwargs 2119): # pylint: disable=unused-argument 2120 2121 """ 2122 Discards the changes applied. 2123 2124 CLI Example: 2125 2126 .. code-block:: bash 2127 2128 salt '*' net.discard_config 2129 """ 2130 2131 return salt.utils.napalm.call( 2132 napalm_device, "discard_config", **{} # pylint: disable=undefined-variable 2133 ) 2134 2135 2136@salt.utils.napalm.proxy_napalm_wrap 2137def compare_config( 2138 inherit_napalm_device=None, **kwargs 2139): # pylint: disable=unused-argument 2140 2141 """ 2142 Returns the difference between the running config and the candidate config. 2143 2144 CLI Example: 2145 2146 .. code-block:: bash 2147 2148 salt '*' net.compare_config 2149 """ 2150 2151 return salt.utils.napalm.call( 2152 napalm_device, "compare_config", **{} # pylint: disable=undefined-variable 2153 ) 2154 2155 2156@salt.utils.napalm.proxy_napalm_wrap 2157def rollback(inherit_napalm_device=None, **kwargs): # pylint: disable=unused-argument 2158 2159 """ 2160 Rollbacks the configuration. 2161 2162 CLI Example: 2163 2164 .. code-block:: bash 2165 2166 salt '*' net.rollback 2167 """ 2168 2169 return salt.utils.napalm.call( 2170 napalm_device, "rollback", **{} # pylint: disable=undefined-variable 2171 ) 2172 2173 2174@salt.utils.napalm.proxy_napalm_wrap 2175def config_changed( 2176 inherit_napalm_device=None, **kwargs 2177): # pylint: disable=unused-argument 2178 2179 """ 2180 Will prompt if the configuration has been changed. 2181 2182 :return: A tuple with a boolean that specifies if the config was changed on the device.\ 2183 And a string that provides more details of the reason why the configuration was not changed. 2184 2185 CLI Example: 2186 2187 .. code-block:: bash 2188 2189 salt '*' net.config_changed 2190 """ 2191 2192 is_config_changed = False 2193 reason = "" 2194 # pylint: disable=undefined-variable 2195 try_compare = compare_config(inherit_napalm_device=napalm_device) 2196 # pylint: enable=undefined-variable 2197 2198 if try_compare.get("result"): 2199 if try_compare.get("out"): 2200 is_config_changed = True 2201 else: 2202 reason = "Configuration was not changed on the device." 2203 else: 2204 reason = try_compare.get("comment") 2205 2206 return is_config_changed, reason 2207 2208 2209@salt.utils.napalm.proxy_napalm_wrap 2210def config_control( 2211 inherit_napalm_device=None, **kwargs 2212): # pylint: disable=unused-argument 2213 2214 """ 2215 Will check if the configuration was changed. 2216 If differences found, will try to commit. 2217 In case commit unsuccessful, will try to rollback. 2218 2219 :return: A tuple with a boolean that specifies if the config was changed/committed/rollbacked on the device.\ 2220 And a string that provides more details of the reason why the configuration was not committed properly. 2221 2222 CLI Example: 2223 2224 .. code-block:: bash 2225 2226 salt '*' net.config_control 2227 """ 2228 2229 result = True 2230 comment = "" 2231 2232 # pylint: disable=undefined-variable 2233 changed, not_changed_rsn = config_changed(inherit_napalm_device=napalm_device) 2234 # pylint: enable=undefined-variable 2235 if not changed: 2236 return (changed, not_changed_rsn) 2237 2238 # config changed, thus let's try to commit 2239 try_commit = commit() 2240 if not try_commit.get("result"): 2241 result = False 2242 comment = ( 2243 "Unable to commit the changes: {reason}.\nWill try to rollback now!".format( 2244 reason=try_commit.get("comment") 2245 ) 2246 ) 2247 try_rollback = rollback() 2248 if not try_rollback.get("result"): 2249 comment += "\nCannot rollback! {reason}".format( 2250 reason=try_rollback.get("comment") 2251 ) 2252 2253 return result, comment 2254 2255 2256def cancel_commit(jid): 2257 """ 2258 .. versionadded:: 2019.2.0 2259 2260 Cancel a commit scheduled to be executed via the ``commit_in`` and 2261 ``commit_at`` arguments from the 2262 :py:func:`net.load_template <salt.modules.napalm_network.load_template>` or 2263 :py:func:`net.load_config <salt.modules.napalm_network.load_config>` 2264 execution functions. The commit ID is displayed when the commit is scheduled 2265 via the functions named above. 2266 2267 CLI Example: 2268 2269 .. code-block:: bash 2270 2271 salt '*' net.cancel_commit 20180726083540640360 2272 """ 2273 job_name = "__napalm_commit_{}".format(jid) 2274 removed = __salt__["schedule.delete"](job_name) 2275 if removed["result"]: 2276 saved = __salt__["schedule.save"]() 2277 removed["comment"] = "Commit #{jid} cancelled.".format(jid=jid) 2278 else: 2279 removed["comment"] = "Unable to find commit #{jid}.".format(jid=jid) 2280 return removed 2281 2282 2283def confirm_commit(jid): 2284 """ 2285 .. versionadded:: 2019.2.0 2286 2287 Confirm a commit scheduled to be reverted via the ``revert_in`` and 2288 ``revert_at`` arguments from the 2289 :mod:`net.load_template <salt.modules.napalm_network.load_template>` or 2290 :mod:`net.load_config <salt.modules.napalm_network.load_config>` 2291 execution functions. The commit ID is displayed when the commit confirmed 2292 is scheduled via the functions named above. 2293 2294 CLI Example: 2295 2296 .. code-block:: bash 2297 2298 salt '*' net.confirm_commit 20180726083540640360 2299 """ 2300 if __grains__["os"] == "junos": 2301 # Confirm the commit, by committing (i.e., invoking the RPC call) 2302 confirmed = __salt__["napalm.junos_commit"]() 2303 confirmed["result"] = confirmed.pop("out") 2304 confirmed["comment"] = confirmed.pop("message") 2305 else: 2306 confirmed = cancel_commit(jid) 2307 if confirmed["result"]: 2308 confirmed["comment"] = "Commit #{jid} confirmed.".format(jid=jid) 2309 return confirmed 2310 2311 2312def save_config(source=None, path=None): 2313 """ 2314 .. versionadded:: 2019.2.0 2315 2316 Save the configuration to a file on the local file system. 2317 2318 source: ``running`` 2319 The configuration source. Choose from: ``running``, ``candidate``, 2320 ``startup``. Default: ``running``. 2321 2322 path 2323 Absolute path to file where to save the configuration. 2324 To push the files to the Master, use 2325 :mod:`cp.push <salt.modules.cp.push>` Execution function. 2326 2327 CLI Example: 2328 2329 .. code-block:: bash 2330 2331 salt '*' net.save_config source=running 2332 """ 2333 if not source: 2334 source = "running" 2335 if not path: 2336 path = salt.utils.files.mkstemp() 2337 running_config = __salt__["net.config"](source=source) 2338 if not running_config or not running_config["result"]: 2339 log.error("Unable to retrieve the config") 2340 return running_config 2341 with salt.utils.files.fopen(path, "w") as fh_: 2342 fh_.write(running_config["out"][source]) 2343 return { 2344 "result": True, 2345 "out": path, 2346 "comment": "{source} config saved to {path}".format(source=source, path=path), 2347 } 2348 2349 2350def replace_pattern( 2351 pattern, 2352 repl, 2353 count=0, 2354 flags=8, 2355 bufsize=1, 2356 append_if_not_found=False, 2357 prepend_if_not_found=False, 2358 not_found_content=None, 2359 search_only=False, 2360 show_changes=True, 2361 backslash_literal=False, 2362 source=None, 2363 path=None, 2364 test=False, 2365 replace=True, 2366 debug=False, 2367 commit=True, 2368): 2369 """ 2370 .. versionadded:: 2019.2.0 2371 2372 Replace occurrences of a pattern in the configuration source. If 2373 ``show_changes`` is ``True``, then a diff of what changed will be returned, 2374 otherwise a ``True`` will be returned when changes are made, and ``False`` 2375 when no changes are made. 2376 This is a pure Python implementation that wraps Python's :py:func:`~re.sub`. 2377 2378 pattern 2379 A regular expression, to be matched using Python's 2380 :py:func:`~re.search`. 2381 2382 repl 2383 The replacement text. 2384 2385 count: ``0`` 2386 Maximum number of pattern occurrences to be replaced. If count is a 2387 positive integer ``n``, only ``n`` occurrences will be replaced, 2388 otherwise all occurrences will be replaced. 2389 2390 flags (list or int): ``8`` 2391 A list of flags defined in the ``re`` module documentation from the 2392 Python standard library. Each list item should be a string that will 2393 correlate to the human-friendly flag name. E.g., ``['IGNORECASE', 2394 'MULTILINE']``. Optionally, ``flags`` may be an int, with a value 2395 corresponding to the XOR (``|``) of all the desired flags. Defaults to 2396 8 (which supports 'MULTILINE'). 2397 2398 bufsize (int or str): ``1`` 2399 How much of the configuration to buffer into memory at once. The 2400 default value ``1`` processes one line at a time. The special value 2401 ``file`` may be specified which will read the entire file into memory 2402 before processing. 2403 2404 append_if_not_found: ``False`` 2405 If set to ``True``, and pattern is not found, then the content will be 2406 appended to the file. 2407 2408 prepend_if_not_found: ``False`` 2409 If set to ``True`` and pattern is not found, then the content will be 2410 prepended to the file. 2411 2412 not_found_content 2413 Content to use for append/prepend if not found. If None (default), uses 2414 ``repl``. Useful when ``repl`` uses references to group in pattern. 2415 2416 search_only: ``False`` 2417 If set to true, this no changes will be performed on the file, and this 2418 function will simply return ``True`` if the pattern was matched, and 2419 ``False`` if not. 2420 2421 show_changes: ``True`` 2422 If ``True``, return a diff of changes made. Otherwise, return ``True`` 2423 if changes were made, and ``False`` if not. 2424 2425 backslash_literal: ``False`` 2426 Interpret backslashes as literal backslashes for the repl and not 2427 escape characters. This will help when using append/prepend so that 2428 the backslashes are not interpreted for the repl on the second run of 2429 the state. 2430 2431 source: ``running`` 2432 The configuration source. Choose from: ``running``, ``candidate``, or 2433 ``startup``. Default: ``running``. 2434 2435 path 2436 Save the temporary configuration to a specific path, then read from 2437 there. 2438 2439 test: ``False`` 2440 Dry run? If set as ``True``, will apply the config, discard and return 2441 the changes. Default: ``False`` and will commit the changes on the 2442 device. 2443 2444 commit: ``True`` 2445 Commit the configuration changes? Default: ``True``. 2446 2447 debug: ``False`` 2448 Debug mode. Will insert a new key in the output dictionary, as 2449 ``loaded_config`` containing the raw configuration loaded on the device. 2450 2451 replace: ``True`` 2452 Load and replace the configuration. Default: ``True``. 2453 2454 If an equal sign (``=``) appears in an argument to a Salt command it is 2455 interpreted as a keyword argument in the format ``key=val``. That 2456 processing can be bypassed in order to pass an equal sign through to the 2457 remote shell command by manually specifying the kwarg: 2458 2459 .. code-block:: bash 2460 2461 salt '*' net.replace_pattern "bind-address\\s*=" "bind-address:" 2462 2463 CLI Example: 2464 2465 .. code-block:: bash 2466 2467 salt '*' net.replace_pattern PREFIX-LIST_NAME new-prefix-list-name 2468 salt '*' net.replace_pattern bgp-group-name new-bgp-group-name count=1 2469 """ 2470 config_saved = save_config(source=source, path=path) 2471 if not config_saved or not config_saved["result"]: 2472 return config_saved 2473 path = config_saved["out"] 2474 replace_pattern = __salt__["file.replace"]( 2475 path, 2476 pattern, 2477 repl, 2478 count=count, 2479 flags=flags, 2480 bufsize=bufsize, 2481 append_if_not_found=append_if_not_found, 2482 prepend_if_not_found=prepend_if_not_found, 2483 not_found_content=not_found_content, 2484 search_only=search_only, 2485 show_changes=show_changes, 2486 backslash_literal=backslash_literal, 2487 ) 2488 with salt.utils.files.fopen(path, "r") as fh_: 2489 updated_config = fh_.read() 2490 return __salt__["net.load_config"]( 2491 text=updated_config, test=test, debug=debug, replace=replace, commit=commit 2492 ) 2493 2494 2495def blockreplace( 2496 marker_start, 2497 marker_end, 2498 content="", 2499 append_if_not_found=False, 2500 prepend_if_not_found=False, 2501 show_changes=True, 2502 append_newline=False, 2503 source="running", 2504 path=None, 2505 test=False, 2506 commit=True, 2507 debug=False, 2508 replace=True, 2509): 2510 """ 2511 .. versionadded:: 2019.2.0 2512 2513 Replace content of the configuration source, delimited by the line markers. 2514 2515 A block of content delimited by comments can help you manage several lines 2516 without worrying about old entries removal. 2517 2518 marker_start 2519 The line content identifying a line as the start of the content block. 2520 Note that the whole line containing this marker will be considered, 2521 so whitespace or extra content before or after the marker is included 2522 in final output. 2523 2524 marker_end 2525 The line content identifying a line as the end of the content block. 2526 Note that the whole line containing this marker will be considered, 2527 so whitespace or extra content before or after the marker is included 2528 in final output. 2529 2530 content 2531 The content to be used between the two lines identified by 2532 ``marker_start`` and ``marker_stop``. 2533 2534 append_if_not_found: ``False`` 2535 If markers are not found and set to True then, the markers and content 2536 will be appended to the file. 2537 2538 prepend_if_not_found: ``False`` 2539 If markers are not found and set to True then, the markers and content 2540 will be prepended to the file. 2541 2542 append_newline: ``False`` 2543 Controls whether or not a newline is appended to the content block. 2544 If the value of this argument is ``True`` then a newline will be added 2545 to the content block. If it is ``False``, then a newline will not be 2546 added to the content block. If it is ``None`` then a newline will only 2547 be added to the content block if it does not already end in a newline. 2548 2549 show_changes: ``True`` 2550 Controls how changes are presented. If ``True``, this function will 2551 return the of the changes made. 2552 If ``False``, then it will return a boolean (``True`` if any changes 2553 were made, otherwise False). 2554 2555 source: ``running`` 2556 The configuration source. Choose from: ``running``, ``candidate``, or 2557 ``startup``. Default: ``running``. 2558 2559 path: ``None`` 2560 Save the temporary configuration to a specific path, then read from 2561 there. This argument is optional, can be used when you prefers a 2562 particular location of the temporary file. 2563 2564 test: ``False`` 2565 Dry run? If set as ``True``, will apply the config, discard and return 2566 the changes. Default: ``False`` and will commit the changes on the 2567 device. 2568 2569 commit: ``True`` 2570 Commit the configuration changes? Default: ``True``. 2571 2572 debug: ``False`` 2573 Debug mode. Will insert a new key in the output dictionary, as 2574 ``loaded_config`` containing the raw configuration loaded on the device. 2575 2576 replace: ``True`` 2577 Load and replace the configuration. Default: ``True``. 2578 2579 CLI Example: 2580 2581 .. code-block:: bash 2582 2583 salt '*' net.blockreplace 'ntp' 'interface' '' 2584 """ 2585 config_saved = save_config(source=source, path=path) 2586 if not config_saved or not config_saved["result"]: 2587 return config_saved 2588 path = config_saved["out"] 2589 replace_pattern = __salt__["file.blockreplace"]( 2590 path, 2591 marker_start=marker_start, 2592 marker_end=marker_end, 2593 content=content, 2594 append_if_not_found=append_if_not_found, 2595 prepend_if_not_found=prepend_if_not_found, 2596 show_changes=show_changes, 2597 append_newline=append_newline, 2598 ) 2599 with salt.utils.files.fopen(path, "r") as fh_: 2600 updated_config = fh_.read() 2601 return __salt__["net.load_config"]( 2602 text=updated_config, test=test, debug=debug, replace=replace, commit=commit 2603 ) 2604 2605 2606def patch( 2607 patchfile, 2608 options="", 2609 saltenv="base", 2610 source_hash=None, 2611 show_changes=True, 2612 source="running", 2613 path=None, 2614 test=False, 2615 commit=True, 2616 debug=False, 2617 replace=True, 2618): 2619 """ 2620 .. versionadded:: 2019.2.0 2621 2622 Apply a patch to the configuration source, and load the result into the 2623 running config of the device. 2624 2625 patchfile 2626 A patch file to apply to the configuration source. 2627 2628 options 2629 Options to pass to patch. 2630 2631 source_hash 2632 If the patch file (specified via the ``patchfile`` argument) is an 2633 HTTP(S) or FTP URL and the file exists in the minion's file cache, this 2634 option can be passed to keep the minion from re-downloading the file if 2635 the cached copy matches the specified hash. 2636 2637 show_changes: ``True`` 2638 Controls how changes are presented. If ``True``, this function will 2639 return the of the changes made. 2640 If ``False``, then it will return a boolean (``True`` if any changes 2641 were made, otherwise False). 2642 2643 source: ``running`` 2644 The configuration source. Choose from: ``running``, ``candidate``, or 2645 ``startup``. Default: ``running``. 2646 2647 path: ``None`` 2648 Save the temporary configuration to a specific path, then read from 2649 there. This argument is optional, can the user prefers a particular 2650 location of the temporary file. 2651 2652 test: ``False`` 2653 Dry run? If set as ``True``, will apply the config, discard and return 2654 the changes. Default: ``False`` and will commit the changes on the 2655 device. 2656 2657 commit: ``True`` 2658 Commit the configuration changes? Default: ``True``. 2659 2660 debug: ``False`` 2661 Debug mode. Will insert a new key in the output dictionary, as 2662 ``loaded_config`` containing the raw configuration loaded on the device. 2663 2664 replace: ``True`` 2665 Load and replace the configuration. Default: ``True``. 2666 2667 CLI Example: 2668 2669 .. code-block:: bash 2670 2671 salt '*' net.patch https://example.com/running_config.patch 2672 """ 2673 config_saved = save_config(source=source, path=path) 2674 if not config_saved or not config_saved["result"]: 2675 return config_saved 2676 path = config_saved["out"] 2677 patchfile_cache = __salt__["cp.cache_file"](patchfile) 2678 if patchfile_cache is False: 2679 return { 2680 "out": None, 2681 "result": False, 2682 "comment": 'The file "{}" does not exist.'.format(patchfile), 2683 } 2684 replace_pattern = __salt__["file.patch"](path, patchfile_cache, options=options) 2685 with salt.utils.files.fopen(path, "r") as fh_: 2686 updated_config = fh_.read() 2687 return __salt__["net.load_config"]( 2688 text=updated_config, test=test, debug=debug, replace=replace, commit=commit 2689 ) 2690 2691 2692# <---- Configuration specific functions ------------------------------------------------------------------------------- 2693