Repository: ansible/ansible

Issue:
IndexError when trying to install a non-existing package (pacman, package)
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
* package
* pacman

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /home/user/code/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.11 (default, Mar 20 2016, 15:46:50) [GCC 5.3.0]
```

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
Host: Arch Linux
Node: Arch Linux

##### SUMMARY
The pacman and package modules fail with a Traceback when trying to install a non-existing package.

##### STEPS TO REPRODUCE
* A playbook with the following content:
```yaml
---
- hosts: all
  tasks:
    - name: Try to install a non-existing package
      package:
        name: thispackagedoesnotexist
        state: latest
```
* An inventory that references a single Arch Linux host.

##### EXPECTED RESULTS
A warning/error message that the package does not exist.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
```
$ ansible-playbook -i inventories/development.ini installtraceback.yml                                                                                                              
PLAY [all] ************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************************************************
ok: [devel]

TASK [Try to install a non-existing package] **************************************************************************************************************************************************************************************************
fatal: [devel]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Shared connection to 192.168.122.125 closed.\r\n", "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_nQG_Kl/ansible_module_pacman.py\", line 451, in <module>\r\n    main()\r\n  File \"/tmp/ansible_nQG_Kl/ansible_module_pacman.py\", line 443, in main\r\n    install_packages(module, pacman_path, p['state'], pkgs, pkg_files)\r\n  File \"/tmp/ansible_nQG_Kl/ansible_module_pacman.py\", line 306, in install_packages\r\n    data = stdout.split('\\n')[3].split(' ')[2:]\r\nIndexError: list index out of range\r\n", "msg": "MODULE FAILURE", "rc": 0}
        to retry, use: --limit @/home/user/code/ansible/installtraceback.retry

PLAY RECAP ************************************************************************************************************************************************************************************************************************************
devel            : ok=1    changed=0    unreachable=0    failed=1 
```


Discussion:
I suspect where it is invoking `'pacman -S %s --noconfirm --noprogressbar --needed' % repos`, that is failing in a way that the `data = stdout.split('\\n')[3].split(' ')[2:]` hits the index error. (less than 4 lines of output would do it, as well as the 4th line not having more than two spaces...)
pinging @indrajitr

Resolving patch:
diff --git a/lib/ansible/modules/packaging/os/pacman.py b/lib/ansible/modules/packaging/os/pacman.py
--- a/lib/ansible/modules/packaging/os/pacman.py
+++ b/lib/ansible/modules/packaging/os/pacman.py
@@ -256,6 +256,9 @@ def remove_packages(module, pacman_path, packages):
         cmd = "%s -%s %s --noconfirm --noprogressbar" % (pacman_path, args, package)
         rc, stdout, stderr = module.run_command(cmd, check_rc=False)
 
+        if rc != 0:
+            module.fail_json(msg="failed to remove %s" % (package))
+
         if module._diff:
             d = stdout.split('\n')[2].split(' ')[2:]
             for i, pkg in enumerate(d):
@@ -263,9 +266,6 @@ def remove_packages(module, pacman_path, packages):
                 diff['before'] += "%s\n" % pkg
             data.append('\n'.join(d))
 
-        if rc != 0:
-            module.fail_json(msg="failed to remove %s" % (package))
-
         remove_c += 1
 
     if remove_c > 0:
@@ -303,6 +303,10 @@ def install_packages(module, pacman_path, state, packages, package_files):
     if to_install_repos:
         cmd = "%s -S %s --noconfirm --noprogressbar --needed" % (pacman_path, " ".join(to_install_repos))
         rc, stdout, stderr = module.run_command(cmd, check_rc=False)
+
+        if rc != 0:
+            module.fail_json(msg="failed to install %s: %s" % (" ".join(to_install_repos), stderr))
+
         data = stdout.split('\n')[3].split(' ')[2:]
         data = [ i for i in data if i != '' ]
         for i, pkg in enumerate(data):
@@ -310,14 +314,15 @@ def install_packages(module, pacman_path, state, packages, package_files):
             if module._diff:
                 diff['after'] += "%s\n" % pkg
 
-        if rc != 0:
-            module.fail_json(msg="failed to install %s: %s" % (" ".join(to_install_repos), stderr))
-
         install_c += len(to_install_repos)
 
     if to_install_files:
         cmd = "%s -U %s --noconfirm --noprogressbar --needed" % (pacman_path, " ".join(to_install_files))
         rc, stdout, stderr = module.run_command(cmd, check_rc=False)
+
+        if rc != 0:
+            module.fail_json(msg="failed to install %s: %s" % (" ".join(to_install_files), stderr))
+
         data = stdout.split('\n')[3].split(' ')[2:]
         data = [ i for i in data if i != '' ]
         for i, pkg in enumerate(data):
@@ -325,9 +330,6 @@ def install_packages(module, pacman_path, state, packages, package_files):
             if module._diff:
                 diff['after'] += "%s\n" % pkg
 
-        if rc != 0:
-            module.fail_json(msg="failed to install %s: %s" % (" ".join(to_install_files), stderr))
-
         install_c += len(to_install_files)
 
     if state == 'latest' and len(package_err) > 0:


Test patch: