Skip to content
Snippets Groups Projects
Commit 10bf0f15 authored by Mohammad Torkashvand's avatar Mohammad Torkashvand
Browse files

add config_mode private to juniper

parent a3c0db3f
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: junos_config
author: Peter Sprygada (@privateip)
short_description: Manage configuration on devices running Juniper JUNOS
description:
- This module provides an implementation for working with the active configuration
running on Juniper JUNOS devices. It provides a set of arguments for loading configuration,
performing rollback operations and zeroing the active configuration on the device.
version_added: 1.0.0
extends_documentation_fragment:
- junipernetworks.junos.junos
options:
lines:
description:
- This argument takes a list of C(set) or C(delete) configuration lines to push
into the remote device. Each line must start with either C(set) or C(delete). This
argument is mutually exclusive with the I(src) argument.
type: list
aliases:
- commands
elements: str
src:
description:
- The I(src) argument provides a path to the configuration file to load into the
remote system. The path can either be a full system path to the configuration
file if the value starts with / or relative to the root of the implemented role
or playbook. This argument is mutually exclusive with the I(lines) argument.
type: path
src_format:
description:
- The I(src_format) argument specifies the format of the configuration found int
I(src). If the I(src_format) argument is not provided, the module will attempt
to determine the format of the configuration file specified in I(src).
type: str
choices:
- xml
- set
- text
- json
rollback:
description:
- The C(rollback) argument instructs the module to rollback the current configuration
to the identifier specified in the argument. If the specified rollback identifier
does not exist on the remote device, the module will fail. To rollback to the
most recent commit, set the C(rollback) argument to 0.
type: int
zeroize:
description:
- The C(zeroize) argument is used to completely sanitize the remote device configuration
back to initial defaults. This argument will effectively remove all current
configuration statements on the remote device.
type: bool
default: false
confirm:
description:
- The C(confirm) argument will configure a time out value in minutes for the commit
to be confirmed before it is automatically rolled back. If the C(confirm) argument
is set to False, this argument is silently ignored. If the value for this argument
is set to 0, the commit is confirmed immediately.
type: int
default: 0
comment:
description:
- The C(comment) argument specifies a text string to be used when committing the
configuration. If the C(confirm) argument is set to False, this argument is
silently ignored.
default: configured by junos_config
type: str
replace:
description:
- The C(replace) argument will instruct the remote device to replace the current
configuration hierarchy with the one specified in the corresponding hierarchy
of the source configuration loaded from this module.
- Note this argument should be considered deprecated. To achieve the equivalent,
set the I(update) argument to C(replace). This argument will be removed in a
future release. The C(replace) and C(update) argument is mutually exclusive.
type: bool
backup:
description:
- This argument will cause the module to create a full backup of the current C(running-config)
from the remote device before any changes are made. If the C(backup_options)
value is not given, the backup file is written to the C(backup) folder in the
playbook root directory or role root directory, if playbook is part of an ansible
role. If the directory does not exist, it is created.
type: bool
default: false
update:
description:
- This argument will decide how to load the configuration data particularly when
the candidate configuration and loaded configuration contain conflicting statements.
Following are accepted values. C(merge) combines the data in the loaded configuration
with the candidate configuration. If statements in the loaded configuration
conflict with statements in the candidate configuration, the loaded statements
replace the candidate ones. C(override) discards the entire candidate configuration
and replaces it with the loaded configuration. C(replace) substitutes each hierarchy
level in the loaded configuration for the corresponding level. C(update) is
similar to the override option. The new configuration completely replaces the
existing configuration. The difference comes when the configuration is later
committed. This option performs a 'diff' between the new candidate configuration
and the existing committed configuration. It then only notifies system processes
responsible for the changed portions of the configuration, and only marks the
actual configuration changes as 'changed'.
type: str
default: merge
choices:
- merge
- override
- replace
- update
confirm_commit:
description:
- This argument will execute commit operation on remote device. It can be used
to confirm a previous commit.
type: bool
default: false
check_commit:
description:
- This argument will check correctness of syntax; do not apply changes.
- Note that this argument can be used to confirm verified configuration done via
commit confirmed operation
type: bool
default: false
backup_options:
description:
- This is a dict object containing configurable options related to backup file
path. The value of this option is read only when C(backup) is set to I(true),
if C(backup) is set to I(false) this option will be silently ignored.
suboptions:
filename:
description:
- The filename to be used to store the backup configuration. If the filename
is not given it will be generated based on the hostname, current time and
date in format defined by <hostname>_config.<current-date>@<current-time>
type: str
dir_path:
description:
- This option provides the path ending with directory name in which the backup
configuration file will be stored. If the directory does not exist it will
be first created and the filename is either the value of C(filename) or
default filename as described in C(filename) options description. If the
path value is not given in that case a I(backup) directory will be created
in the current working directory and backup configuration will be copied
in C(filename) within I(backup) directory.
type: path
backup_format:
description:
- This argument specifies the format of the configuration the backup file will
be stored as. If the argument is not specified, the module will use the 'set'
format.
type: str
default: set
choices:
- xml
- set
- text
- json
type: dict
config_mode:
description:
- If set to “private”, open a private candidate datastore instead of the global one.
type: str
choices: [ private ]
required: false
default: null
requirements:
- ncclient (>=v0.5.2)
notes:
- This module requires the netconf system service be enabled on the remote device
being managed.
- Abbreviated commands are NOT idempotent, see L(Network FAQ,../network/user_guide/faq.html)
- Loading JSON-formatted configuration I(json) is supported starting in Junos OS Release
16.1 onwards.
- Update C(override) not currently compatible with C(set) notation.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
- Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
- This module also works with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
- name: load configure file into device
junipernetworks.junos.junos_config:
src: srx.cfg
comment: update config
- name: load configure lines into device
junipernetworks.junos.junos_config:
lines:
- set interfaces ge-0/0/1 unit 0 description "Test interface"
- set vlans vlan01 description "Test vlan"
comment: update config
- name: Set routed VLAN interface (RVI) IPv4 address
junipernetworks.junos.junos_config:
lines:
- set vlans vlan01 vlan-id 1
- set interfaces irb unit 10 family inet address 10.0.0.1/24
- set vlans vlan01 l3-interface irb.10
- name: Check correctness of commit configuration
junipernetworks.junos.junos_config:
check_commit: true
- name: rollback the configuration to id 10
junipernetworks.junos.junos_config:
rollback: 10
- name: zero out the current configuration
junipernetworks.junos.junos_config:
zeroize: true
- name: Set VLAN access and trunking
junipernetworks.junos.junos_config:
lines:
- set vlans vlan02 vlan-id 6
- set interfaces ge-0/0/6.0 family ethernet-switching interface-mode access vlan
members vlan02
- set interfaces ge-0/0/6.0 family ethernet-switching interface-mode trunk vlan
members vlan02
- name: confirm a previous commit
junipernetworks.junos.junos_config:
confirm_commit: true
- name: for idempotency, use full-form commands
junipernetworks.junos.junos_config:
lines:
# - set int ge-0/0/1 unit 0 desc "Test interface"
- set interfaces ge-0/0/1 unit 0 description "Test interface"
- name: configurable backup path
junipernetworks.junos.junos_config:
src: srx.cfg
backup: true
backup_options:
filename: backup.cfg
dir_path: /home/user
"""
RETURN = """
backup_path:
description: The full path to the backup file
returned: when backup is true
type: str
sample: /playbooks/ansible/backup/config.2016-07-16@22:28:34
filename:
description: The name of the backup file
returned: when backup is true and filename is not specified in backup options
type: str
sample: junos01_config.2016-07-16@22:28:34
shortname:
description: The full path to the backup file excluding the timestamp
returned: when backup is true and filename is not specified in backup options
type: str
sample: /playbooks/ansible/backup/junos01_config
date:
description: The date extracted from the backup file name
returned: when backup is true
type: str
sample: "2016-07-16"
time:
description: The time extracted from the backup file name
returned: when backup is true
type: str
sample: "22:28:34"
"""
import json
import re
from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import string_types
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.netconf import (
exec_rpc,
)
from ansible_collections.junipernetworks.junos.plugins.module_utils.network.junos.junos import (
commit_configuration,
discard_changes,
get_configuration,
get_diff,
load_config,
load_configuration,
locked_config,
tostring,
)
try:
from lxml.etree import Element, fromstring
except ImportError:
from xml.etree.ElementTree import Element, fromstring
try:
from lxml.etree import ParseError
except ImportError:
try:
from xml.etree.ElementTree import ParseError
except ImportError:
# for Python < 2.7
from xml.parsers.expat import ExpatError
ParseError = ExpatError
USE_PERSISTENT_CONNECTION = True
DEFAULT_COMMENT = "configured by junos_config"
def check_args(module, warnings):
if module.params["replace"] is not None:
module.fail_json(msg="argument replace is deprecated, use update")
def zeroize(module):
return exec_rpc(
module,
tostring(Element("request-system-zeroize")),
ignore_warning=False,
)
def rollback(ele, id="0"):
return get_diff(ele, id)
def guess_format(config):
try:
json.loads(config)
return "json"
except ValueError:
pass
try:
fromstring(config)
return "xml"
except ParseError:
pass
if config.startswith("set") or config.startswith("delete"):
return "set"
return "text"
def filter_delete_statements(module, candidate):
reply = get_configuration(module, format="set")
match = reply.find(".//configuration-set")
if match is None:
# Could not find configuration-set in reply, perhaps device does not support it?
return candidate
config = to_native(match.text, encoding="latin-1")
modified_candidate = candidate[:]
for index, line in reversed(list(enumerate(candidate))):
if line.startswith("delete"):
newline = re.sub("^delete", "set", line)
if newline not in config:
del modified_candidate[index]
return modified_candidate
def configure_device(module, warnings, candidate):
kwargs = {}
config_format = None
if module.params["src"]:
config_format = module.params["src_format"] or guess_format(
str(candidate),
)
if config_format == "set":
kwargs.update({"format": "text", "action": "set"})
else:
kwargs.update(
{"format": config_format, "action": module.params["update"]},
)
if isinstance(candidate, string_types):
candidate = candidate.split("\n")
# this is done to filter out `delete ...` statements which map to
# nothing in the config as that will cause an exception to be raised
if any((module.params["lines"], config_format == "set")):
candidate = filter_delete_statements(module, candidate)
kwargs["format"] = "text"
kwargs["action"] = "set"
return load_config(module, candidate, warnings, **kwargs)
def main():
"""main entry point for module execution"""
backup_spec = dict(
filename=dict(),
dir_path=dict(type="path"),
backup_format=dict(
default="set",
choices=["xml", "text", "set", "json"],
),
)
argument_spec = dict(
lines=dict(aliases=["commands"], type="list", elements="str"),
src=dict(type="path"),
src_format=dict(choices=["xml", "text", "set", "json"]),
# update operations
update=dict(
default="merge",
choices=["merge", "override", "replace", "update"],
),
# deprecated replace in Ansible 2.3
replace=dict(type="bool"),
confirm=dict(default=0, type="int"),
comment=dict(default=DEFAULT_COMMENT),
confirm_commit=dict(type="bool", default=False),
check_commit=dict(type="bool", default=False),
# config operations
backup=dict(type="bool", default=False),
backup_options=dict(type="dict", options=backup_spec),
rollback=dict(type="int"),
zeroize=dict(default=False, type="bool"),
config_mode=dict(type='str', choices=['private'], required=False, default=None),
)
mutually_exclusive = [("lines", "src", "rollback", "zeroize")]
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
)
# if the user asked for private config mode, tell the connection
cfg_mode = module.params.get("config_mode")
if cfg_mode:
open_rpc = (
'<open-configuration '
'xmlns="http://xml.juniper.net/xnm/1.1/xnm" '
'private="true"/>'
)
try:
module._connection.exec_command(open_rpc)
except Exception as e:
module.fail_json(msg=f"Failed to open private candidate: {to_text(e)}")
warnings = list()
check_args(module, warnings)
candidate = module.params["lines"] or module.params["src"]
commit = not module.check_mode
result = {"changed": False, "warnings": warnings}
if module.params["backup"]:
if module.params["backup_options"] is not None:
conf_format = module.params["backup_options"]["backup_format"]
else:
conf_format = "set"
reply = get_configuration(module, format=conf_format)
if reply is None:
module.fail_json(msg="unable to retrieve device configuration")
else:
if conf_format in ["set", "text"]:
reply = reply.find(
".//configuration-%s" % conf_format,
).text.strip()
elif conf_format in "xml":
reply = str(
tostring(reply.find(".//configuration"), pretty_print=True),
).strip()
elif conf_format in "json":
reply = str(reply.xpath("//rpc-reply/text()")[0]).strip()
if not isinstance(reply, str):
module.fail_json(
msg="unable to format retrieved device configuration",
)
result["__backup__"] = reply
rollback_id = module.params["rollback"]
if isinstance(rollback_id, int) and rollback_id >= 0:
diff = rollback(module, rollback_id)
if commit:
kwargs = {"comment": module.params["comment"]}
with locked_config(module):
load_configuration(module, rollback=rollback_id)
commit_configuration(module, **kwargs)
if module._diff:
result["diff"] = {"prepared": diff}
result["changed"] = True
elif module.params["zeroize"]:
if commit:
zeroize(module)
result["changed"] = True
else:
if candidate:
with locked_config(module):
diff = configure_device(module, warnings, candidate)
if diff:
if commit:
kwargs = {
"comment": module.params["comment"],
"check": module.params["check_commit"],
}
confirm = module.params["confirm"]
if confirm > 0:
kwargs.update(
{
"confirm": True,
"confirm_timeout": to_text(
confirm,
errors="surrogate_then_replace",
),
},
)
commit_configuration(module, **kwargs)
else:
discard_changes(module)
result["changed"] = True
if module._diff:
result["diff"] = {"prepared": diff}
elif module.params["check_commit"]:
commit_configuration(module, check=True)
elif module.params["confirm_commit"]:
with locked_config(module):
# confirm a previous commit
commit_configuration(module)
result["changed"] = True
if cfg_mode == 'private':
close_rpc = (
'<close-configuration '
'xmlns="http://xml.juniper.net/xnm/1.1/xnm"/>'
)
try:
module._connection.exec_command(close_rpc)
except Exception:
pass # session teardown will close it anyway
module.exit_json(**result)
if __name__ == "__main__":
main()
#
# (c) 2017 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
author: Ansible Networking Team (@ansible-network)
name: junos
short_description: Use junos netconf plugin to run netconf commands on Juniper JUNOS
platform
description:
- This junos plugin provides low level abstraction apis for sending and receiving
netconf commands from Juniper JUNOS network devices.
version_added: 1.0.0
options:
ncclient_device_handler:
type: str
default: junos
description:
- Specifies the ncclient device handler name for Juniper junos network os. To
identify the ncclient device handler name refer ncclient library documentation.
"""
import json
import re
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.six import string_types
from ansible_collections.ansible.netcommon.plugins.plugin_utils.netconf_base import (
NetconfBase,
ensure_ncclient,
)
try:
from ncclient import manager
from ncclient.operations import RPCError
from ncclient.transport.errors import SSHUnknownHostError
from ncclient.xml_ import new_ele, sub_ele, to_ele, to_xml
HAS_NCCLIENT = True
except (
ImportError,
AttributeError,
): # paramiko and gssapi are incompatible and raise AttributeError not ImportError
HAS_NCCLIENT = False
class Netconf(NetconfBase):
def get_text(self, ele, tag):
try:
return to_text(
ele.find(tag).text,
errors="surrogate_then_replace",
).strip()
except AttributeError:
pass
@ensure_ncclient
def get_device_info(self):
device_info = dict()
device_info["network_os"] = "junos"
ele = new_ele("get-software-information")
data = self.execute_rpc(to_xml(ele))
reply = to_ele(data)
sw_info = reply.find(".//software-information")
device_info["network_os_version"] = self.get_text(
sw_info,
"junos-version",
)
device_info["network_os_hostname"] = self.get_text(
sw_info,
"host-name",
)
device_info["network_os_model"] = self.get_text(
sw_info,
"product-model",
)
return device_info
def execute_rpc(self, name):
"""
RPC to be execute on remote device
:param name: Name of rpc in string format
:return: Received rpc response from remote host
"""
return self.rpc(name)
@ensure_ncclient
def load_configuration(
self,
format="xml",
action="merge",
target="candidate",
config=None,
):
"""
Load given configuration on device
:param format: Format of configuration (xml, text, set)
:param action: Action to be performed (merge, replace, override, update)
:param target: The name of the configuration datastore being edited
:param config: The configuration to be loaded on remote host in string format
:return: Received rpc response from remote host in string format
"""
if config:
if format == "xml":
config = to_ele(config)
try:
return self.m.load_configuration(
format=format,
action=action,
target=target,
config=config,
).data_xml
except RPCError as exc:
raise Exception(to_xml(exc.xml))
def get_capabilities(self):
result = dict()
result["rpc"] = self.get_base_rpc() + [
"commit",
"discard_changes",
"validate",
"lock",
"unlock",
"copy_copy",
"execute_rpc",
"load_configuration",
"get_configuration",
"command",
"reboot",
"halt",
]
result["network_api"] = "netconf"
result["device_info"] = self.get_device_info()
result["server_capabilities"] = list(self.m.server_capabilities)
result["client_capabilities"] = list(self.m.client_capabilities)
result["session_id"] = self.m.session_id
result["device_operations"] = self.get_device_operations(
result["server_capabilities"],
)
return json.dumps(result)
@staticmethod
@ensure_ncclient
def guess_network_os(obj):
"""
Guess the remote network os name
:param obj: Netconf connection class object
:return: Network OS name
"""
try:
m = manager.connect(
host=obj._play_context.remote_addr,
port=obj._play_context.port or 830,
username=obj._play_context.remote_user,
password=obj._play_context.password,
key_filename=obj.key_filename,
hostkey_verify=obj.get_option("host_key_checking"),
look_for_keys=obj.get_option("look_for_keys"),
allow_agent=obj._play_context.allow_agent,
timeout=obj.get_option("persistent_connect_timeout"),
# We need to pass in the path to the ssh_config file when guessing
# the network_os so that a jumphost is correctly used if defined
ssh_config=obj._ssh_config,
)
except SSHUnknownHostError as exc:
raise AnsibleConnectionFailure(to_native(exc))
guessed_os = None
for c in m.server_capabilities:
if re.search("junos", c):
guessed_os = "junos"
m.close_session()
return guessed_os
def get_configuration(self, format="xml", filter=None):
"""
Retrieve all or part of a specified configuration.
:param format: format in which configuration should be retrieved
:param filter: specifies the portion of the configuration to retrieve
as either xml string rooted in <configuration> element
:return: Received rpc response from remote host in string format
"""
if filter is not None:
if not isinstance(filter, string_types):
raise AnsibleConnectionFailure(
"get configuration filter should be of type string,"
" received value '%s' is of type '%s'" % (filter, type(filter)),
)
filter = to_ele(filter)
return self.m.get_configuration(format=format, filter=filter).data_xml
def compare_configuration(self, rollback=0):
"""
Compare the candidate configuration with running configuration
by default. The candidate configuration can be compared with older
committed configuration by providing rollback id.
:param rollback: Rollback id of previously commited configuration
:return: Received rpc response from remote host in string format
"""
return self.m.compare_configuration(rollback=rollback).data_xml
def halt(self):
"""reboot the device"""
return self.m.halt().data_xml
def reboot(self):
"""reboot the device"""
return self.m.reboot().data_xml
# Due to issue in ncclient commit() method for Juniper (https://github.com/ncclient/ncclient/issues/238)
# below commit() is a workaround which build's raw `commit-configuration` xml with required tags and uses
# ncclient generic rpc() method to execute rpc on remote host.
# Remove below method after the issue in ncclient is fixed.
@ensure_ncclient
def commit(
self,
confirmed=False,
timeout=None,
persist=None,
check=False,
comment=None,
synchronize=False,
at_time=None,
):
"""
Commit the candidate configuration as the device's new current configuration.
Depends on the `:candidate` capability.
A confirmed commit (i.e. if *confirmed* is `True`) is reverted if there is no
followup commit within the *timeout* interval. If no timeout is specified the
confirm timeout defaults to 600 seconds (10 minutes).
A confirming commit may have the *confirmed* parameter but this is not required.
Depends on the `:confirmed-commit` capability.
:param confirmed: whether this is a confirmed commit
:param check: Check correctness of syntax
:param timeout: specifies the confirm timeout in seconds
:param comment: Message to write to commit log
:param synchronize: Synchronize commit on remote peers
:param at_time: Time at which to activate configuration changes
:return: Received rpc response from remote host
"""
obj = new_ele("commit-configuration")
if confirmed:
sub_ele(obj, "confirmed")
if check:
sub_ele(obj, "check")
if synchronize:
sub_ele(obj, "synchronize")
if at_time:
subele = sub_ele(obj, "at-time")
subele.text = str(at_time)
if comment:
subele = sub_ele(obj, "log")
subele.text = str(comment)
if timeout:
subele = sub_ele(obj, "confirm-timeout")
subele.text = str(timeout)
return self.rpc(obj)
......@@ -103,7 +103,3 @@ class Netconf(NetconfBase):
resp = self.m.commit(confirmed=confirmed, timeout=timeout, persist=persist, comment=comment)
return resp.data_xml if hasattr(resp, "data_xml") else resp.xml
def set_config_mode(self, config_mode):
"""Set the config_mode passed from the module."""
if config_mode:
self._config_mode = config_mode
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment