diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py
index c40320462d..c1415dd9d1 100644
--- a/lib/ansible/constants.py
+++ b/lib/ansible/constants.py
@@ -329,7 +329,8 @@ DEFAULT_TEST_PLUGIN_PATH = get_config(p, DEFAULTS, 'test_plugins', 'ANSIBL
DEFAULT_STRATEGY_PLUGIN_PATH = get_config(p, DEFAULTS, 'strategy_plugins', 'ANSIBLE_STRATEGY_PLUGINS',
'~/.ansible/plugins/strategy:/usr/share/ansible/plugins/strategy', value_type='pathlist')
-NETWORK_GROUP_MODULES = get_config(p, DEFAULTS, 'network_group_modules','NETWORK_GROUP_MODULES', ['eos', 'nxos', 'ios', 'iosxr', 'junos', 'vyos'],
+NETWORK_GROUP_MODULES = get_config(p, DEFAULTS, 'network_group_modules','NETWORK_GROUP_MODULES', ['eos', 'nxos', 'ios', 'iosxr', 'junos',
+ 'vyos', 'sros'],
value_type='list')
DEFAULT_STRATEGY = get_config(p, DEFAULTS, 'strategy', 'ANSIBLE_STRATEGY', 'linear')
diff --git a/lib/ansible/module_utils/netcfg.py b/lib/ansible/module_utils/netcfg.py
index abdbffd784..885205381e 100644
--- a/lib/ansible/module_utils/netcfg.py
+++ b/lib/ansible/module_utils/netcfg.py
@@ -30,7 +30,7 @@ import re
from ansible.module_utils.six.moves import zip
from ansible.module_utils.network_common import to_list
-DEFAULT_COMMENT_TOKENS = ['#', '!', '/*', '*/']
+DEFAULT_COMMENT_TOKENS = ['#', '!', '/*', '*/', 'echo']
class ConfigLine(object):
diff --git a/lib/ansible/module_utils/sros.py b/lib/ansible/module_utils/sros.py
index 87d36fd0a8..bbb8679cd0 100644
--- a/lib/ansible/module_utils/sros.py
+++ b/lib/ansible/module_utils/sros.py
@@ -28,82 +28,93 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-import re
+from ansible.module_utils.basic import env_fallback
+from ansible.module_utils.network_common import to_list, ComplexList
+from ansible.module_utils.connection import exec_command
-from ansible.module_utils.network import NetworkModule, NetworkError
-from ansible.module_utils.network import register_transport, to_list
-from ansible.module_utils.shell import CliBase
-from ansible.module_utils.netcli import Command
+_DEVICE_CONFIGS = {}
+
+sros_argument_spec = {
+ 'host': dict(),
+ 'port': dict(type='int'),
+ 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
+ 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
+ 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
+ 'timeout': dict(type='int'),
+ 'provider': dict(type='dict')
+}
+
+def check_args(module, warnings):
+ provider = module.params['provider'] or {}
+ for key in sros_argument_spec:
+ if key != 'provider' and module.params[key]:
+ warnings.append('argument %s has been deprecated and will be '
+ 'removed in a future version' % key)
+
+def get_config(module, flags=[]):
+ cmd = 'admin display-config '
+ cmd += ' '.join(flags)
+ cmd = cmd.strip()
+
+ try:
+ return _DEVICE_CONFIGS[cmd]
+ except KeyError:
+ rc, out, err = exec_command(module, cmd)
+ if rc != 0:
+ module.fail_json(msg='unable to retrieve current config', stderr=err)
+ cfg = str(out).strip()
+ _DEVICE_CONFIGS[cmd] = cfg
+ return cfg
+
+def to_commands(module, commands):
+ spec = {
+ 'command': dict(key=True),
+ 'prompt': dict(),
+ 'answer': dict()
+ }
+ transform = ComplexList(spec, module)
+ return transform(commands)
-class Cli(CliBase):
+def run_commands(module, commands, check_rc=True):
+ responses = list()
+ commands = to_commands(module, to_list(commands))
+ for cmd in commands:
+ cmd = module.jsonify(cmd)
+ rc, out, err = exec_command(module, cmd)
+ if check_rc and rc != 0:
+ module.fail_json(msg=err, rc=rc)
+ responses.append(out)
+ return responses
- NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
+def load_config(module, commands):
+ for command in to_list(commands):
+ rc, out, err = exec_command(module, command)
+ if rc != 0:
+ module.fail_json(msg=err, command=command, rc=rc)
+ exec_command(module, 'exit all')
- CLI_PROMPTS_RE = [
- re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
- re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
- ]
-
- CLI_ERRORS_RE = [
- re.compile(r"^\r\nError:", re.M),
- ]
-
- def __init__(self):
- super(Cli, self).__init__()
- self._rollback_enabled = None
-
- @property
- def rollback_enabled(self):
- if self._rollback_enabled is not None:
- return self._rollback_enabled
- resp = self.execute(['show system rollback'])
- match = re.search(r'^Rollback Location\s+:\s(\S+)', resp[0], re.M)
- self._rollback_enabled = match.group(1) != 'None'
+def rollback_enabled(self):
+ if self._rollback_enabled is not None:
return self._rollback_enabled
+ resp = self.execute(['show system rollback'])
+ match = re.search(r'^Rollback Location\s+:\s(\S+)', resp[0], re.M)
+ self._rollback_enabled = match.group(1) != 'None'
+ return self._rollback_enabled
- def connect(self, params, **kwargs):
- super(Cli, self).connect(params, kickstart=False, **kwargs)
- self.shell.send('environment no more')
- self._connected = True
+def load_config_w_rollback(self, commands):
+ if self.rollback_enabled:
+ self.execute(['admin rollback save'])
- ### implementation of netcli.Cli ###
-
- def run_commands(self, commands, **kwargs):
- return self.execute(to_list(commands))
-
- ### implementation of netcfg.Config ###
-
- def configure(self, commands, **kwargs):
- cmds = to_list(commands)
- responses = self.execute(cmds)
- self.execute(['exit all'])
- return responses
-
- def get_config(self, detail=False, **kwargs):
- cmd = 'admin display-config'
- if detail:
- cmd += ' detail'
- return self.execute(cmd)[0]
-
- def load_config(self, commands):
+ try:
+ self.configure(commands)
+ except NetworkError:
if self.rollback_enabled:
- self.execute(['admin rollback save'])
+ self.execute(['admin rollback revert latest-rb',
+ 'admin rollback delete latest-rb'])
+ raise
- try:
- self.configure(commands)
- except NetworkError:
- if self.rollback_enabled:
- self.execute(['admin rollback revert latest-rb',
- 'admin rollback delete latest-rb'])
- raise
-
- if self.rollback_enabled:
- self.execute(['admin rollback delete latest-rb'])
-
- def save_config(self):
- self.execute(['admin save'])
-
-Cli = register_transport('cli', default=True)(Cli)
+ if self.rollback_enabled:
+ self.execute(['admin rollback delete latest-rb'])
diff --git a/lib/ansible/modules/network/sros/sros_command.py b/lib/ansible/modules/network/sros/sros_command.py
index 4fa84d88a2..ec8d18bebe 100644
--- a/lib/ansible/modules/network/sros/sros_command.py
+++ b/lib/ansible/modules/network/sros/sros_command.py
@@ -16,10 +16,11 @@
# along with Ansible. If not, see .
#
-ANSIBLE_METADATA = {'metadata_version': '1.0',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.0',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
DOCUMENTATION = """
---
@@ -143,34 +144,45 @@ failed_conditions:
type: list
sample: ['...', '...']
"""
-from ansible.module_utils.basic import get_exception
-from ansible.module_utils.netcli import CommandRunner
-from ansible.module_utils.netcli import AddCommandError, FailedConditionsError
-from ansible.module_utils.sros import NetworkModule, NetworkError
+import time
-VALID_KEYS = ['command', 'output', 'prompt', 'response']
+from ansible.module_utils.sros import run_commands
+from ansible.module_utils.sros import sros_argument_spec, check_args
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import ComplexList
+from ansible.module_utils.netcli import Conditional
+from ansible.module_utils.six import string_types
def to_lines(stdout):
for item in stdout:
- if isinstance(item, basestring):
+ if isinstance(item, string_types):
item = str(item).split('\n')
yield item
-def parse_commands(module):
- for cmd in module.params['commands']:
- if isinstance(cmd, basestring):
- cmd = dict(command=cmd, output=None)
- elif 'command' not in cmd:
- module.fail_json(msg='command keyword argument is required')
- elif cmd.get('output') not in [None, 'text']:
- module.fail_json(msg='invalid output specified for command')
- elif not set(cmd.keys()).issubset(VALID_KEYS):
- module.fail_json(msg='unknown keyword specified')
- yield cmd
+def parse_commands(module, warnings):
+ command = ComplexList(dict(
+ command=dict(key=True),
+ prompt=dict(),
+ answer=dict()
+ ), module)
+ commands = command(module.params['commands'])
+ for index, item in enumerate(commands):
+ if module.check_mode and not item['command'].startswith('show'):
+ warnings.append(
+ 'only show commands are supported when using check mode, not '
+ 'executing `%s`' % item['command']
+ )
+ elif item['command'].startswith('conf'):
+ module.fail_json(
+ msg='sros_command does not support running config mode '
+ 'commands. Please use sros_config instead'
+ )
+ return commands
def main():
- spec = dict(
- # { command: , output: , prompt: , response: }
+ """main entry point for module execution
+ """
+ argument_spec = dict(
commands=dict(type='list', required=True),
wait_for=dict(type='list', aliases=['waitfor']),
@@ -180,59 +192,52 @@ def main():
interval=dict(default=1, type='int')
)
- module = NetworkModule(argument_spec=spec,
- connect_on_load=False,
+ argument_spec.update(sros_argument_spec)
+
+ module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
- commands = list(parse_commands(module))
- conditionals = module.params['wait_for'] or list()
+ result = {'changed': False}
warnings = list()
-
- runner = CommandRunner(module)
-
- for cmd in commands:
- if module.check_mode and not cmd['command'].startswith('show'):
- warnings.append('only show commands are supported when using '
- 'check mode, not executing `%s`' % cmd['command'])
- else:
- if cmd['command'].startswith('conf'):
- module.fail_json(msg='sros_command does not support running '
- 'config mode commands. Please use '
- 'sros_config instead')
- try:
- runner.add_command(**cmd)
- except AddCommandError:
- exc = get_exception()
- warnings.append('duplicate command detected: %s' % cmd)
-
- for item in conditionals:
- runner.add_conditional(item)
-
- runner.retries = module.params['retries']
- runner.interval = module.params['interval']
- runner.match = module.params['match']
-
- try:
- runner.run()
- except FailedConditionsError:
- exc = get_exception()
- module.fail_json(msg=str(exc), failed_conditions=exc.failed_conditions)
- except NetworkError:
- exc = get_exception()
- module.fail_json(msg=str(exc))
-
- result = dict(changed=False, stdout=list())
-
- for cmd in commands:
- try:
- output = runner.get_command(cmd['command'])
- except ValueError:
- output = 'command not executed due to check_mode, see warnings'
- result['stdout'].append(output)
-
+ check_args(module, warnings)
+ commands = parse_commands(module, warnings)
result['warnings'] = warnings
- result['stdout_lines'] = list(to_lines(result['stdout']))
+
+ wait_for = module.params['wait_for'] or list()
+ conditionals = [Conditional(c) for c in wait_for]
+
+ retries = module.params['retries']
+ interval = module.params['interval']
+ match = module.params['match']
+
+ while retries > 0:
+ responses = run_commands(module, commands)
+
+ for item in list(conditionals):
+ if item(responses):
+ if match == 'any':
+ conditionals = list()
+ break
+ conditionals.remove(item)
+
+ if not conditionals:
+ break
+
+ time.sleep(interval)
+ retries -= 1
+
+ if conditionals:
+ failed_conditions = [item.raw for item in conditionals]
+ msg = 'One or more conditional statements have not be satisfied'
+ module.fail_json(msg=msg, failed_conditions=failed_conditions)
+
+
+ result = {
+ 'changed': False,
+ 'stdout': responses,
+ 'stdout_lines': list(to_lines(responses))
+ }
module.exit_json(**result)
diff --git a/lib/ansible/modules/network/sros/sros_config.py b/lib/ansible/modules/network/sros/sros_config.py
index 48964e0e46..6d7366431c 100644
--- a/lib/ansible/modules/network/sros/sros_config.py
+++ b/lib/ansible/modules/network/sros/sros_config.py
@@ -16,9 +16,11 @@
# along with Ansible. If not, see .
#
-ANSIBLE_METADATA = {'metadata_version': '1.0',
- 'status': ['preview'],
- 'supported_by': 'community'}
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.0',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
DOCUMENTATION = """
@@ -203,16 +205,22 @@ updates:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
- sample: ['...', '...']
+ sample: ['config system name "sros01"']
+commands:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['config system name "sros01"']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: path
sample: /playbooks/ansible/backup/sros_config.2016-07-16@22:28:34
"""
-from ansible.module_utils.basic import get_exception
-from ansible.module_utils.sros import NetworkModule, NetworkError
+from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netcfg import NetworkConfig, dumps
+from ansible.module_utils.sros import sros_argument_spec, check_args
+from ansible.module_utils.sros import load_config, run_commands, get_config
def sanitize_config(lines):
commands = list()
@@ -224,15 +232,17 @@ def sanitize_config(lines):
commands.append(line)
return commands
-def get_config(module, result):
+def get_active_config(module):
contents = module.params['config']
if not contents:
- defaults = module.params['defaults']
- contents = module.config.get_config(detail=defaults)
- return NetworkConfig(device_os='sros', contents=contents)
+ flags = []
+ if module.params['defaults']:
+ flags = ['detail']
+ return get_config(module, flags)
+ return contents
def get_candidate(module):
- candidate = NetworkConfig(device_os='sros')
+ candidate = NetworkConfig(indent=4)
if module.params['src']:
candidate.load(module.params['src'])
elif module.params['lines']:
@@ -246,33 +256,23 @@ def run(module, result):
candidate = get_candidate(module)
if match != 'none':
- config = get_config(module, result)
+ config_text = get_active_config(module)
+ config = NetworkConfig(indent=4, contents=config_text)
configobjs = candidate.difference(config)
else:
configobjs = candidate.items
if configobjs:
- commands = dumps(configobjs, 'lines')
+ commands = dumps(configobjs, 'commands')
commands = sanitize_config(commands.split('\n'))
+ result['commands'] = commands
result['updates'] = commands
- # check if creating checkpoints is possible
- if not module.connection.rollback_enabled:
- warn = 'Cannot create checkpoint. Please enable this feature ' \
- 'using the sros_rollback module. Automatic rollback ' \
- 'will be disabled'
- result['warnings'].append(warn)
-
# send the configuration commands to the device and merge
# them with the current running config
if not module.check_mode:
- module.config.load_config(commands)
- result['changed'] = True
-
- if module.params['save']:
- if not module.check_mode:
- module.config.save_config()
+ load_config(module, commands)
result['changed'] = True
def main():
@@ -293,23 +293,30 @@ def main():
save=dict(type='bool', default=False),
)
+ argument_spec.update(sros_argument_spec)
+
mutually_exclusive = [('lines', 'src')]
- module = NetworkModule(argument_spec=argument_spec,
- connect_on_load=False,
+ module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
result = dict(changed=False, warnings=list())
- if module.params['backup']:
- result['__backup__'] = module.config.get_config()
+ warnings = list()
+ check_args(module, warnings)
+ if warnings:
+ result['warnings'] = warnings
- try:
- run(module, result)
- except NetworkError:
- exc = get_exception()
- module.fail_json(msg=str(exc), **exc.kwargs)
+ if module.params['backup']:
+ result['__backup__'] = get_config(module)
+
+ run(module, result)
+
+ if module.params['save']:
+ if not module.check_mode:
+ run_commands(module, ['admin save'])
+ result['changed'] = True
module.exit_json(**result)
diff --git a/lib/ansible/modules/network/sros/sros_rollback.py b/lib/ansible/modules/network/sros/sros_rollback.py
index 6a283174be..b6746c0e9c 100644
--- a/lib/ansible/modules/network/sros/sros_rollback.py
+++ b/lib/ansible/modules/network/sros/sros_rollback.py
@@ -16,9 +16,11 @@
# along with Ansible. If not, see .
#
-ANSIBLE_METADATA = {'metadata_version': '1.0',
- 'status': ['preview'],
- 'supported_by': 'community'}
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.0',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
DOCUMENTATION = """
@@ -107,10 +109,12 @@ updates:
type: list
sample: ['...', '...']
"""
-from ansible.module_utils.basic import get_exception
-from ansible.module_utils.sros import NetworkModule, NetworkError
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.sros import load_config, get_config
+from ansible.module_utils.sros import sros_argument_spec, check_args
from ansible.module_utils.netcfg import NetworkConfig, dumps
+
def invoke(name, *args, **kwargs):
func = globals().get(name)
if func:
@@ -136,7 +140,7 @@ def present(module, commands):
invoke(setter, module, commands)
def absent(module, commands):
- config = module.config.get_config()
+ config = get_config(module)
if 'rollback-location' in config:
commands.append('configure system rollback no rollback-location')
if 'rescue-location' in config:
@@ -166,27 +170,9 @@ def set_rescue_location(module, commands):
value = module.params['rescue_location']
commands.append('configure system rollback rescue-location "%s"' % value)
-def get_config(module):
- contents = module.config.get_config()
- return NetworkConfig(device_os='sros', contents=contents)
-
-def load_config(module, commands, result):
- candidate = NetworkConfig(device_os='sros', contents='\n'.join(commands))
- config = get_config(module)
- configobjs = candidate.difference(config)
-
- if configobjs:
- commands = dumps(configobjs, 'lines')
- commands = sanitize_config(commands.split('\n'))
-
- result['updates'] = commands
-
- # send the configuration commands to the device and merge
- # them with the current running config
- if not module.check_mode:
- module.config(commands)
-
- result['changed'] = True
+def get_device_config(module):
+ contents = get_config(module)
+ return NetworkConfig(indent=4, contents=contents)
def main():
""" main entry point for module execution
@@ -202,8 +188,9 @@ def main():
state=dict(default='present', choices=['present', 'absent'])
)
- module = NetworkModule(argument_spec=argument_spec,
- connect_on_load=False,
+ argument_spec.update(sros_argument_spec)
+
+ module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
state = module.params['state']
@@ -213,11 +200,24 @@ def main():
commands = list()
invoke(state, module, commands)
- try:
- load_config(module, commands, result)
- except NetworkError:
- exc = get_exception()
- module.fail_json(msg=str(exc), **exc.kwargs)
+ candidate = NetworkConfig(indent=4, contents='\n'.join(commands))
+ config = get_device_config(module)
+ configobjs = candidate.difference(config)
+
+ if configobjs:
+ #commands = dumps(configobjs, 'lines')
+ commands = dumps(configobjs, 'commands')
+ commands = sanitize_config(commands.split('\n'))
+
+ result['updates'] = commands
+ result['commands'] = commands
+
+ # send the configuration commands to the device and merge
+ # them with the current running config
+ if not module.check_mode:
+ load_config(module, commands)
+
+ result['changed'] = True
module.exit_json(**result)
diff --git a/lib/ansible/plugins/action/sros.py b/lib/ansible/plugins/action/sros.py
new file mode 100644
index 0000000000..49738cd0ce
--- /dev/null
+++ b/lib/ansible/plugins/action/sros.py
@@ -0,0 +1,111 @@
+#
+# (c) 2016 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 .
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import sys
+import copy
+
+from ansible.plugins.action.normal import ActionModule as _ActionModule
+from ansible.utils.path import unfrackpath
+from ansible.plugins import connection_loader
+from ansible.module_utils.sros import sros_argument_spec
+from ansible.module_utils.basic import AnsibleFallbackNotFound
+from ansible.module_utils.six import iteritems
+from ansible.module_utils._text import to_bytes
+
+try:
+ from __main__ import display
+except ImportError:
+ from ansible.utils.display import Display
+ display = Display()
+
+
+class ActionModule(_ActionModule):
+
+ def run(self, tmp=None, task_vars=None):
+
+ if self._play_context.connection != 'local':
+ return dict(
+ failed=True,
+ msg='invalid connection specified, expected connection=local, '
+ 'got %s' % self._play_context.connection
+ )
+
+ provider = self.load_provider()
+
+ pc = copy.deepcopy(self._play_context)
+ pc.connection = 'network_cli'
+ pc.network_os = 'sros'
+ pc.remote_addr = provider['host'] or self._play_context.remote_addr
+ pc.port = provider['port'] or self._play_context.port or 22
+ pc.remote_user = provider['username'] or self._play_context.connection_user
+ pc.password = provider['password'] or self._play_context.password
+ pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
+ pc.timeout = provider['timeout'] or self._play_context.timeout
+
+ display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
+ connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
+
+ socket_path = self._get_socket_path(pc)
+ display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
+
+ if not os.path.exists(socket_path):
+ # start the connection if it isn't started
+ rc, out, err = connection.exec_command('open_shell()')
+ if not rc == 0:
+ return {'failed': True, 'msg': 'unable to open shell', 'rc': rc}
+
+ task_vars['ansible_socket'] = socket_path
+
+ return super(ActionModule, self).run(tmp, task_vars)
+
+ def _get_socket_path(self, play_context):
+ ssh = connection_loader.get('ssh', class_only=True)
+ cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user)
+ path = unfrackpath("$HOME/.ansible/pc")
+ return cp % dict(directory=path)
+
+ def load_provider(self):
+ provider = self._task.args.get('provider', {})
+ for key, value in iteritems(sros_argument_spec):
+ if key != 'provider' and key not in provider:
+ if key in self._task.args:
+ provider[key] = self._task.args[key]
+ elif 'fallback' in value:
+ provider[key] = self._fallback(value['fallback'])
+ elif key not in provider:
+ provider[key] = None
+ return provider
+
+ def _fallback(self, fallback):
+ strategy = fallback[0]
+ args = []
+ kwargs = {}
+
+ for item in fallback[1:]:
+ if isinstance(item, dict):
+ kwargs = item
+ else:
+ args = item
+ try:
+ return strategy(*args, **kwargs)
+ except AnsibleFallbackNotFound:
+ pass
diff --git a/lib/ansible/plugins/action/sros_config.py b/lib/ansible/plugins/action/sros_config.py
index c1a9a065b3..dcd84deff4 100644
--- a/lib/ansible/plugins/action/sros_config.py
+++ b/lib/ansible/plugins/action/sros_config.py
@@ -19,10 +19,95 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.plugins.action import ActionBase
-from ansible.plugins.action.net_config import ActionModule as NetActionModule
+import os
+import re
+import time
+import glob
-class ActionModule(NetActionModule, ActionBase):
- pass
+from ansible.plugins.action.sros import ActionModule as _ActionModule
+from ansible.module_utils._text import to_text
+from ansible.module_utils.six.moves.urllib.parse import urlsplit
+from ansible.utils.vars import merge_hash
+
+PRIVATE_KEYS_RE = re.compile('__.+__')
+class ActionModule(_ActionModule):
+
+ def run(self, tmp=None, task_vars=None):
+
+ if self._task.args.get('src'):
+ try:
+ self._handle_template()
+ except ValueError as exc:
+ return dict(failed=True, msg=exc.message)
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+
+ if self._task.args.get('backup') and result.get('__backup__'):
+ # User requested backup and no error occurred in module.
+ # NOTE: If there is a parameter error, _backup key may not be in results.
+ filepath = self._write_backup(task_vars['inventory_hostname'],
+ result['__backup__'])
+
+ result['backup_path'] = filepath
+
+ # strip out any keys that have two leading and two trailing
+ # underscore characters
+ for key in result.keys():
+ if PRIVATE_KEYS_RE.match(key):
+ del result[key]
+
+ return result
+
+ def _get_working_path(self):
+ cwd = self._loader.get_basedir()
+ if self._task._role is not None:
+ cwd = self._task._role._role_path
+ return cwd
+
+ def _write_backup(self, host, contents):
+ backup_path = self._get_working_path() + '/backup'
+ if not os.path.exists(backup_path):
+ os.mkdir(backup_path)
+ for fn in glob.glob('%s/%s*' % (backup_path, host)):
+ os.remove(fn)
+ tstamp = time.strftime("%Y-%m-%d@%H:%M:%S", time.localtime(time.time()))
+ filename = '%s/%s_config.%s' % (backup_path, host, tstamp)
+ open(filename, 'w').write(contents)
+ return filename
+
+ def _handle_template(self):
+ src = self._task.args.get('src')
+ working_path = self._get_working_path()
+
+ if os.path.isabs(src) or urlsplit('src').scheme:
+ source = src
+ else:
+ source = self._loader.path_dwim_relative(working_path, 'templates', src)
+ if not source:
+ source = self._loader.path_dwim_relative(working_path, src)
+
+ if not os.path.exists(source):
+ raise ValueError('path specified in src not found')
+
+ try:
+ with open(source, 'r') as f:
+ template_data = to_text(f.read())
+ except IOError:
+ return dict(failed=True, msg='unable to load src file')
+
+ # Create a template search path in the following order:
+ # [working_path, self_role_path, dependent_role_paths, dirname(source)]
+ searchpath = [working_path]
+ if self._task._role is not None:
+ searchpath.append(self._task._role._role_path)
+ if hasattr(self._task, "_block:"):
+ dep_chain = self._task._block.get_dep_chain()
+ if dep_chain is not None:
+ for role in dep_chain:
+ searchpath.append(role._role_path)
+ searchpath.append(os.path.dirname(source))
+ self._templar.environment.loader.searchpath = searchpath
+ self._task.args['src'] = self._templar.template(template_data)
+
diff --git a/lib/ansible/plugins/terminal/sros.py b/lib/ansible/plugins/terminal/sros.py
index 6eea535f4f..07d8f37b86 100644
--- a/lib/ansible/plugins/terminal/sros.py
+++ b/lib/ansible/plugins/terminal/sros.py
@@ -34,7 +34,7 @@ class TerminalModule(TerminalBase):
]
terminal_stderr_re = [
- re.compile(r"^\r\nError:"),
+ re.compile(r"Error:"),
]
def on_open_shell(self):