Repository: ansible/ansible
Pull request: * adds commit replace with config file for iosxr
* fixes dci failure in iosxr_logging

##### SUMMARY
<!--- Describe the change, including rationale and design decisions -->

fixes #32061 

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->

 - Bugfix Pull Request


##### COMPONENT NAME
iosxr_config
iosxr_logging
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
2.5.0
```




Problem statement:
iosxr_config src parameter does not do what is expected.
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
 - iosxr_config module

##### ANSIBLE VERSION
```
$ ansible --version
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4]

```

##### CONFIGURATION
No settings changed in ansible.cfg
N/A

##### OS / ENVIRONMENT
ubuntu trusty 14.04.5- from where i am running playbook
cisco iosxr:  Version 6.1.2 - router where i want to replace config

##### SUMMARY
The src parameter in iosxr_config module is not working as expected.As mentioned in the example in the module guide:
```yaml
- name: load a config from disk and replace the current config
  iosxr_config:
    src: config.cfg
    backup: yes
```
I am trying to replace the iosxr_config with simple the configuration stored in file(running-config).it does not do "commit replace", instead it merges the configuration. if i use replace paramete like below:
```yaml
- name: load a config from disk and replace the current config
  iosxr_config:
    src: config.cfg
    backup: yes
    replace: config
```
It does not work and throws error.


##### STEPS TO REPRODUCE

set_config.yml

```yaml
---
- hosts: all
  remote_user: cisco
  #become: true
  gather_facts: no
  tasks:
   - name: replacing the iosxr configuration
     iosxr_config:
       src: running-config
       backup: yes
       replace: config
```
ansible_host config:
```yaml
Node_1   ansible_host=172.16.1.100 ansible_port=22 ansible_connection=local ansible_ssh_user=admin ansible_ssh_pass=admin
```
configuration to be replaced(stored in file running-config):

Building configuration...
!! IOS XR Configuration 6.1.2
!! Last configuration change at Tue Oct 24 05:17:00 2017 by cisco
!
hostname Node_1
interface MgmtEth0/0/CPU0/0
 description OOB Management
 ! Configured on launch
 ipv4 address 172.16.1.127 255.255.255.0
!
ssh server v2
ssh server vrf default
ssh server logging
end

##### EXPECTED RESULTS

should replace the configuration on the router(equivalent to "commit replace")

##### ACTUAL RESULTS

```java

$ ansible-playbook -i ansible_hosts set_config.yml 

PLAY [all] ******************************************************************************************************************************************************************

TASK [replacing the iosxr configuration] ************************************************************************************************************************************
fatal: [Node_1]: FAILED! => {"changed": false, "commands": ["Tue Oct 24 05:17:07.664 UTC", "Building configuration...", "hostname Node_1", "interface MgmtEth0/0/CPU0/0", "description OOB Management", "ipv4 address 172.16.1.127 255.255.255.0", "ssh server v2", "ssh server vrf default", "ssh server logging", "end"], "failed": true, "msg": "Tue Oct 24 05:17:07.664 UTC\r\n\r                            ^\r\n% Invalid input detected at '^' marker.\r\nRP/0/0/CPU0:Node_1(config)#", "rc": 1}
	to retry, use: --limit @/home/cisco/gaurav/set_config.retry

PLAY RECAP ******************************************************************************************************************************************************************
Node_1                     : ok=0    changed=0    unreachable=0    failed=1

```

Hints from issue discussion:
cc @Qalthos @ganeshrn @gundalow @kedarX @privateip @rcarrillocruz @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Docs should be backported to `2.4` as well
Any update?
@kedarX can you please verify this is fixed. If it is, please update this issue and close it. Thanks!

Commit message: * adds commit replace with config file for iosxr
* fixes dci failure in iosxr_logging

Patch to review:
diff --git a/lib/ansible/module_utils/network/iosxr/iosxr.py b/lib/ansible/module_utils/network/iosxr/iosxr.py
index 114f848132066e..de30f15c5b5994 100644
--- a/lib/ansible/module_utils/network/iosxr/iosxr.py
+++ b/lib/ansible/module_utils/network/iosxr/iosxr.py
@@ -29,6 +29,7 @@
 import json
 from difflib import Differ
 from copy import deepcopy
+from time import sleep
 
 from ansible.module_utils._text import to_text, to_bytes
 from ansible.module_utils.basic import env_fallback
@@ -415,7 +416,14 @@ def load_config(module, command_filter, commit=False, replace=False,
         if module._diff:
             diff = get_config_diff(module)
 
-        if commit:
+        if replace:
+            cmd = {'command': 'commit replace',
+                   'prompt': 'This commit will replace or remove the entire running configuration',
+                   'answer': 'yes'}
+            conn.edit_config(cmd)
+            sleep(5)
+            conn.edit_config('end')
+        elif commit:
             commit_config(module, comment=comment)
             conn.edit_config('end')
         else:
@@ -428,20 +436,36 @@ def run_command(module, commands):
     conn = get_connection(module)
     responses = list()
     for cmd in to_list(commands):
+
         try:
-            cmd = json.loads(cmd)
-            command = cmd['command']
-            prompt = cmd['prompt']
-            answer = cmd['answer']
+            if isinstance(cmd, str):
+                cmd = json.loads(cmd)
+            command = cmd.get('command', None)
+            prompt = cmd.get('prompt', None)
+            answer = cmd.get('answer', None)
+            sendonly = cmd.get('sendonly', False)
+            newline = cmd.get('newline', True)
         except:
             command = cmd
             prompt = None
             answer = None
+            sendonly = False
+            newline = True
 
-        out = conn.get(command, prompt, answer)
+        out = conn.get(command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
 
         try:
             responses.append(to_text(out, errors='surrogate_or_strict'))
         except UnicodeError:
             module.fail_json(msg=u'failed to decode output from {0}:{1}'.format(cmd, to_text(out)))
     return responses
+
+
+def put_file(module, src, dst, proto='scp'):
+    conn = get_connection(module)
+    conn.put_file_cliconf(source=src, destination=dst, proto=proto)
+
+
+def get_file(module, src, dst, proto='scp'):
+    conn = get_connection(module)
+    conn.fetch_file_cliconf(source=src, destination=dst, proto=proto)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_command.py b/lib/ansible/modules/network/iosxr/iosxr_command.py
index 5fca0fdb26222f..fea39f9ef70143 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_command.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_command.py
@@ -94,7 +94,7 @@
       commands:
         - show version
         - show interfaces
-        - [{ command: example command that prompts, prompt: expected prompt, answer: yes}]
+        - { command: example command that prompts, prompt: expected prompt, answer: yes}
 
   - name: run multiple commands and evaluate the output
     iosxr_command:
diff --git a/lib/ansible/modules/network/iosxr/iosxr_config.py b/lib/ansible/modules/network/iosxr/iosxr_config.py
index 7dc747e661f639..f55191612ff310 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_config.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_config.py
@@ -25,7 +25,9 @@
     a deterministic way.
 extends_documentation_fragment: iosxr
 notes:
-  - Tested against IOS XR 6.1.2
+  - Tested against IOS XRv 6.1.2
+  - Avoid service disrupting changes (viz. Management IP) from config replace.
+  - Do not use C(end) in the replace config file.
 options:
   lines:
     description:
@@ -164,6 +166,7 @@
 - name: load a config from disk and replace the current config
   iosxr_config:
     src: config.cfg
+    replace: config
     backup: yes
 """
 
@@ -181,13 +184,26 @@
 """
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.network.iosxr.iosxr import load_config, get_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, put_file
 from ansible.module_utils.network.common.config import NetworkConfig, dumps
 
-
 DEFAULT_COMMIT_COMMENT = 'configured by iosxr_config'
 
 
+def copy_file_to_node(module):
+    """ Copy config file to IOS-XR node. We use SFTP because older IOS-XR versions don't handle SCP very well.
+    """
+    src = '/tmp/ansible_config.txt'
+    file = open(src, 'wb')
+    file.write(module.params['src'])
+    file.close()
+
+    dst = '/harddisk:/ansible_config.txt'
+    put_file(module, src, dst, 'sftp')
+
+    return True
+
+
 def check_args(module, warnings):
     if module.params['comment']:
         if len(module.params['comment']) > 60:
@@ -224,18 +240,30 @@ def run(module, result):
     admin = module.params['admin']
     check_mode = module.check_mode
 
-    candidate = get_candidate(module)
+    candidate_config = get_candidate(module)
+    running_config = get_running_config(module)
 
+    commands = None
     if match != 'none' and replace != 'config':
-        contents = get_running_config(module)
-        configobj = NetworkConfig(contents=contents, indent=1)
-        commands = candidate.difference(configobj, path=path, match=match,
-                                        replace=replace)
+        commands = candidate_config.difference(running_config, path=path, match=match, replace=replace)
+    elif replace_config:
+        can_config = candidate_config.difference(running_config, path=path, match=match, replace=replace)
+        candidate = dumps(can_config, 'commands').split('\n')
+        run_config = running_config.difference(candidate_config, path=path, match=match, replace=replace)
+        running = dumps(run_config, 'commands').split('\n')
+
+        if len(candidate) > 1 or len(running) > 1:
+            ret = copy_file_to_node(module)
+            if not ret:
+                module.fail_json(msg='Copy of config file to the node failed')
+
+            commands = ['load harddisk:/ansible_config.txt']
     else:
-        commands = candidate.items
+        commands = candidate_config.items
 
     if commands:
-        commands = dumps(commands, 'commands').split('\n')
+        if not replace_config:
+            commands = dumps(commands, 'commands').split('\n')
 
         if any((module.params['lines'], module.params['src'])):
             if module.params['before']:
@@ -247,10 +275,10 @@ def run(module, result):
             result['commands'] = commands
 
         commit = not check_mode
-        diff = load_config(module, commands, commit=commit, replace=replace_config,
-                           comment=comment, admin=admin)
+        diff = load_config(module, commands, commit=commit, replace=replace_config, comment=comment, admin=admin)
         if diff:
             result['diff'] = dict(prepared=diff)
+
         result['changed'] = True
 
 
diff --git a/lib/ansible/modules/network/iosxr/iosxr_logging.py b/lib/ansible/modules/network/iosxr/iosxr_logging.py
index 039fa163e532a2..e1645a912970e7 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_logging.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_logging.py
@@ -586,7 +586,7 @@ def map_obj_to_xml_rpc(self):
                 elif item['dest'] == 'host' and item['name'] in host_list:
                     item['level'] = severity_level[item['level']]
                     host_params.append(item)
-                elif item['dest'] == 'console' and have_console and have_console_enable:
+                elif item['dest'] == 'console' and have_console:
                     console_params.update({'console-level': item['level']})
                 elif item['dest'] == 'monitor' and have_monitor:
                     monitor_params.update({'monitor-level': item['level']})
diff --git a/lib/ansible/plugins/cliconf/__init__.py b/lib/ansible/plugins/cliconf/__init__.py
index 74cac5d54e89cd..901931e14b432a 100644
--- a/lib/ansible/plugins/cliconf/__init__.py
+++ b/lib/ansible/plugins/cliconf/__init__.py
@@ -34,7 +34,6 @@
 except ImportError:
     HAS_SCP = False
 
-
 try:
     from __main__ import display
 except ImportError:
@@ -135,7 +134,7 @@ def get_config(self, source='running', format='text'):
         pass
 
     @abstractmethod
-    def edit_config(self, commands):
+    def edit_config(self, commands=None):
         """Loads the specified commands into the remote device
         This method will load the commands into the remote device.  This
         method will make sure the device is in the proper context before
@@ -150,7 +149,7 @@ def edit_config(self, commands):
         pass
 
     @abstractmethod
-    def get(self, command, prompt=None, answer=None, sendonly=False):
+    def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True):
         """Execute specified command on remote device
         This method will retrieve the specified data and
         return it to the caller as a string.
@@ -181,18 +180,26 @@ def discard_changes(self):
         "Discard changes in candidate datastore"
         return self._connection.method_not_found("discard_changes is not supported by network_os %s" % self._play_context.network_os)
 
-    def put_file(self, source, destination):
-        """Copies file over scp to remote device"""
-        if not HAS_SCP:
-            self._connection.internal_error("Required library scp is not installed.  Please install it using `pip install scp`")
-        ssh = self._connection._connect_uncached()
-        with SCPClient(ssh.get_transport()) as scp:
-            scp.put(source, destination)
-
-    def fetch_file(self, source, destination):
-        """Fetch file over scp from remote device"""
-        if not HAS_SCP:
-            self._connection.internal_error("Required library scp is not installed.  Please install it using `pip install scp`")
-        ssh = self._connection._connect_uncached()
-        with SCPClient(ssh.get_transport()) as scp:
-            scp.get(source, destination)
+    def put_file_cliconf(self, source=None, destination=None, proto='scp'):
+        """Copies file over scp/sftp to remote device"""
+        ssh = self._connection.p._connect_uncached()
+        if proto == 'scp':
+            if not HAS_SCP:
+                self._connection.internal_error("Required library scp is not installed.  Please install it using `pip install scp`")
+            with SCPClient(ssh.get_transport()) as scp:
+                scp.put(source, destination)
+        elif proto == 'sftp':
+            with ssh.open_sftp() as sftp:
+                sftp.put(source, destination)
+
+    def fetch_file_cliconf(self, source=None, destination=None, proto='scp'):
+        """Fetch file over scp/sftp from remote device"""
+        ssh = self._connection.p._connect_uncached()
+        if proto == 'scp':
+            if not HAS_SCP:
+                self._connection.internal_error("Required library scp is not installed.  Please install it using `pip install scp`")
+            with SCPClient(ssh.get_transport()) as scp:
+                scp.get(source, destination)
+        elif proto == 'sftp':
+            with ssh.open_sftp() as sftp:
+                sftp.get(source, destination)
diff --git a/lib/ansible/plugins/cliconf/iosxr.py b/lib/ansible/plugins/cliconf/iosxr.py
index 42aa864487bab2..37afb3f21ca138 100644
--- a/lib/ansible/plugins/cliconf/iosxr.py
+++ b/lib/ansible/plugins/cliconf/iosxr.py
@@ -67,12 +67,27 @@ def get_config(self, source='running', filter=None):
 
         return self.send_command(cmd)
 
-    def edit_config(self, command):
-        for cmd in chain(to_list(command)):
-            self.send_command(cmd)
-
-    def get(self, command, prompt=None, answer=None, sendonly=False):
-        return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly)
+    def edit_config(self, commands=None):
+        for cmd in chain(to_list(commands)):
+            try:
+                if isinstance(cmd, str):
+                    cmd = json.loads(cmd)
+                command = cmd.get('command', None)
+                prompt = cmd.get('prompt', None)
+                answer = cmd.get('answer', None)
+                sendonly = cmd.get('sendonly', False)
+                newline = cmd.get('newline', True)
+            except:
+                command = cmd
+                prompt = None
+                answer = None
+                sendonly = None
+                newline = None
+
+            self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
+
+    def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True):
+        return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
 
     def commit(self, comment=None):
         if comment:
diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py
index 73a2974b4b11cf..23543ce7b15eae 100644
--- a/lib/ansible/plugins/connection/network_cli.py
+++ b/lib/ansible/plugins/connection/network_cli.py
@@ -288,6 +288,8 @@ def _connect(self):
         p.force_persistence = self.force_persistence
         ssh = p._connect()
 
+        self.p = p
+
         display.vvvv('ssh connection done, setting terminal', host=self._play_context.remote_addr)
 
         self._ssh_shell = ssh.ssh.invoke_shell()
diff --git a/test/integration/targets/iosxr_command/tests/cli/invalid.yaml b/test/integration/targets/iosxr_command/tests/cli/invalid.yaml
index d26b9ec18fed19..1f9393ddec6bfd 100644
--- a/test/integration/targets/iosxr_command/tests/cli/invalid.yaml
+++ b/test/integration/targets/iosxr_command/tests/cli/invalid.yaml
@@ -3,7 +3,7 @@
 
 - name: run invalid command
   iosxr_command:
-    commands: [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
+    commands: {command: 'show foo', prompt: 'fooprompt', answer: 'yes'}
   register: result
   ignore_errors: yes
 
@@ -15,7 +15,7 @@
   iosxr_command:
     commands:
       - show version
-      - [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
+      - {command: 'show foo', prompt: 'fooprompt', answer: 'yes'}
   register: result
   ignore_errors: yes
 
diff --git a/test/integration/targets/iosxr_config/fixtures/config_add_interface.txt b/test/integration/targets/iosxr_config/fixtures/config_add_interface.txt
new file mode 100644
index 00000000000000..02bfb160dfa340
--- /dev/null
+++ b/test/integration/targets/iosxr_config/fixtures/config_add_interface.txt
@@ -0,0 +1,33 @@
+hostname iosxr01
+line default
+ transport input ssh
+!
+interface Loopback888
+ description test for ansible
+ shutdown
+!
+interface MgmtEth0/0/CPU0/0
+ ipv4 address dhcp
+!
+interface preconfigure GigabitEthernet0/0/0/3
+ description test-interface-3
+ mtu 256
+ speed 100
+ duplex full
+!
+interface GigabitEthernet0/0/0/0
+ shutdown
+!
+interface GigabitEthernet0/0/0/1
+ shutdown
+!
+router static
+ address-family ipv4 unicast
+  0.0.0.0/0 10.0.2.2
+ !
+!
+netconf-yang agent
+ ssh
+!
+ssh server v2
+ssh server netconf vrf default
diff --git a/test/integration/targets/iosxr_config/fixtures/config_del_interface.txt b/test/integration/targets/iosxr_config/fixtures/config_del_interface.txt
new file mode 100644
index 00000000000000..42a1d74e7b1375
--- /dev/null
+++ b/test/integration/targets/iosxr_config/fixtures/config_del_interface.txt
@@ -0,0 +1,27 @@
+hostname iosxr01
+line default
+ transport input ssh
+!
+interface Loopback888
+ description test for ansible
+ shutdown
+!
+interface MgmtEth0/0/CPU0/0
+ ipv4 address dhcp
+!
+interface GigabitEthernet0/0/0/0
+ shutdown
+!
+interface GigabitEthernet0/0/0/1
+ shutdown
+!
+router static
+ address-family ipv4 unicast
+  0.0.0.0/0 10.0.2.2
+ !
+!
+netconf-yang agent
+ ssh
+!
+ssh server v2
+ssh server netconf vrf default
diff --git a/test/integration/targets/iosxr_config/tests/cli/replace_config.yaml b/test/integration/targets/iosxr_config/tests/cli/replace_config.yaml
new file mode 100644
index 00000000000000..b388099d99a3dd
--- /dev/null
+++ b/test/integration/targets/iosxr_config/tests/cli/replace_config.yaml
@@ -0,0 +1,45 @@
+---
+- debug: msg="START cli/replace_config.yaml on connection={{ ansible_connection }}"
+
+- name: setup
+  iosxr_config:
+    commands:
+      - no interface GigabitEthernet0/0/0/3
+  
+- name: replace config (add preconfigured interface) 
+  iosxr_config: &addreplace
+    src: "{{ role_path }}/fixtures/config_add_interface.txt"
+    replace: config
+    backup: yes
+  register: result
+
+- assert:
+    that:
+      - '"load harddisk:/ansible_config.txt" in result.commands'
+
+- name: replace config (add preconfigured interface) 
+  iosxr_config: *addreplace
+  register: result
+
+- assert: &false
+    that:
+      - 'result.changed == false'
+
+- name: replace config (del preconfigured interface) 
+  iosxr_config: &delreplace
+    src: "{{ role_path }}/fixtures/config_del_interface.txt"
+    replace: config
+    backup: yes
+  register: result
+
+- assert:
+    that:
+      - '"load harddisk:/ansible_config.txt" in result.commands'
+
+- name: replace config (del preconfigured interface) 
+  iosxr_config: *delreplace
+  register: result
+
+- assert: *false
+
+- debug: msg="END cli/replace_config.yaml on connection={{ ansible_connection }}"


Code review comments:
diff_hunk: @@ -415,7 +416,14 @@ def load_config(module, command_filter, commit=False, replace=False,
         if module._diff:
             diff = get_config_diff(module)
 
-        if commit:
+        if replace:
+            cmd = {'command': 'commit replace',
+                   'prompt': 'This commit will replace or remove the entire running configuration',
+                   'answer': 'yes'}
+            conn.edit_config(cmd)
+            sleep(5)
path: lib/ansible/module_utils/network/iosxr/iosxr.py
text: @user1:
Things like this scary me.

@author:
So does me, usually the commit replace operation takes say like a 1 or 2 second to complete. I am not sure if our command are sent async; assuming they are async, I added a delay here. With a small config file it works just fine without delay too. Maybe I will comment this out for now, with a note stating add a delay if a failure occurs in future (which I hope won't). BTW this delay is only during replace config operation

@user2:
afaik after sending command to remote host, we wait for response till default command timeout is triggered (30 sec)

diff_hunk: @@ -181,18 +180,26 @@ def discard_changes(self):
         "Discard changes in candidate datastore"
         return self._connection.method_not_found("discard_changes is not supported by network_os %s" % self._play_context.network_os)
 
-    def put_file(self, source, destination):
-        """Copies file over scp to remote device"""
-        if not HAS_SCP:
-            self._connection.internal_error("Required library scp is not installed.  Please install it using `pip install scp`")
-        ssh = self._connection._connect_uncached()
-        with SCPClient(ssh.get_transport()) as scp:
-            scp.put(source, destination)
-
-    def fetch_file(self, source, destination):
-        """Fetch file over scp from remote device"""
-        if not HAS_SCP:
-            self._connection.internal_error("Required library scp is not installed.  Please install it using `pip install scp`")
-        ssh = self._connection._connect_uncached()
-        with SCPClient(ssh.get_transport()) as scp:
-            scp.get(source, destination)
+    def put_file_cliconf(self, source=None, destination=None, proto='scp'):
path: lib/ansible/plugins/cliconf/__init__.py
text: @user1:
Related to above comment on API name

diff_hunk: @@ -288,6 +288,8 @@ def _connect(self):
         p.force_persistence = self.force_persistence
         ssh = p._connect()
 
+        self.p = p
path: lib/ansible/plugins/connection/network_cli.py
text: @user1:
`p` can be removed completely and `self.p` can be renamed to a meaningful name something like`self.paramiko_conn`

diff_hunk: @@ -428,20 +436,36 @@ def run_command(module, commands):
     conn = get_connection(module)
     responses = list()
     for cmd in to_list(commands):
+
         try:
-            cmd = json.loads(cmd)
-            command = cmd['command']
-            prompt = cmd['prompt']
-            answer = cmd['answer']
+            if isinstance(cmd, str):
+                cmd = json.loads(cmd)
+            command = cmd.get('command', None)
+            prompt = cmd.get('prompt', None)
+            answer = cmd.get('answer', None)
+            sendonly = cmd.get('sendonly', False)
+            newline = cmd.get('newline', True)
         except:
             command = cmd
             prompt = None
             answer = None
+            sendonly = False
+            newline = True
 
-        out = conn.get(command, prompt, answer)
+        out = conn.get(command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
 
         try:
             responses.append(to_text(out, errors='surrogate_or_strict'))
         except UnicodeError:
             module.fail_json(msg=u'failed to decode output from {0}:{1}'.format(cmd, to_text(out)))
     return responses
+
+
+def put_file(module, src, dst, proto='scp'):
+    conn = get_connection(module)
+    conn.put_file_cliconf(source=src, destination=dst, proto=proto)
path: lib/ansible/module_utils/network/iosxr/iosxr.py
text: @user1:
Can we rename this to `copy_file` and `get_file`. Can't think of a reason why API name should have connection type in it.