Repository: ansible/ansible

Issue:
htpasswd module: traceback with badly-formatted (invalid?) htpasswd files
When trying to use the `htpasswd` module with a htpasswd file formatted as below, ansible returns the message "msg: need more than 1 value to unpack". I found this confusing and it took me a long time to work out as I thought the error was with the playbook or variables. The file doesn't seem to cause any problems for nginx basic auth, which increased my confusion.

```

user:cryptpass

```

I'd be happy if ansible just said that there was some error passing the current htpasswd file. 



Discussion:
Can you please post the complete htpasswd file? 

It sounds as though you are saying the file format was invalid previously.

I agree the traceback is suboptimal but I want to confirm here.

Also as per contributing.md, what is the output of "ansible --version" ?

Thanks!

An example htpasswd would be

```

user:us0OYDvjnlMyQ

```

It probably is invalid, but I had no idea before as it was generated in another tool and worked fine with nginx. You interpreted correctly; that's how it was before using ansible to manage it.

The version is 'ansible 1.4.1'.

thanks!

The htpasswd seems perfectly valid to me, so I wonder why it fail.
Can you paste the output of the following script: 

```
#!/usr/bin/python
import passlib
from passlib.apache import HtpasswdFile
HtpasswdFile('path/to/the/htpasswd/file')
```

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/passlib/apache.py", line 551, in __init__
    super(HtpasswdFile, self).__init__(path, **kwds)
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/passlib/apache.py", line 166, in __init__
    self.load()
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/passlib/apache.py", line 236, in load
    self._load_lines(fh)
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/passlib/apache.py", line 261, in _load_lines
    key, value = parse(line, idx+1)
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/passlib/apache.py", line 558, in _parse_record
    % lineno)
ValueError: malformed htpasswd file (error reading line 1)
```

Well, that error message is very clear. :)

It work fine for me using the extract you gave in your 2nd comment, so I am a bit puzzled.

Did you copy the empty first line as well?

Nope, but it wasn't obvious this was important, that make a lot more sense now.

Hi!

Thanks very much for your interest in Ansible.  It sincerely means a lot to us. 

On September 26, 2014, due to enormous levels of contribution to the project Ansible decided to reorganize module repos, making it easier
for developers to work on the project and for us to more easily manage new contributions and tickets.

We split modules from the main project off into two repos, http://github.com/ansible/ansible-modules-core and http://github.com/ansible/ansible-modules-extras

If you would still like this ticket attended to, and believe this problem or idea is still present in the latest version of Ansible (1.7.2) or the development branch, we will need your help in having it reopened in one of the two new repos, and instructions are provided below.

We apologize that we are not able to make this transition happen seamlessly, though this is a one-time change and your help is greatly appreciated -- 
this will greatly improve velocity going forward.

Both sets of modules will ship with Ansible, though they'll receive slightly different ticket handling.

To locate where a module lives between 'core' and 'extras'
- Find the module at http://docs.ansible.com/list_of_all_modules.html
- Open the documentation page for that module
- If the bottom of the docs say "This is an extras module", submit your ticket to https://github.com/ansible/ansible-modules-extras
- Otherwise, submit your pull request to update the existing module to https://github.com/ansible/ansible-modules-core
- action_plugins (modules with server side components) still live in the main repo.  If your ticket affects both, open the ticket
   on the module repo just the same.

Additionally, should you need more help with this, you can ask questions on:
- the ansible-project mailing list: https://groups.google.com/forum/#!forum/ansible-project

Thank you very much!


Resolving patch:
diff --git a/lib/ansible/modules/web_infrastructure/apache2_module.py b/lib/ansible/modules/web_infrastructure/apache2_module.py
--- a/lib/ansible/modules/web_infrastructure/apache2_module.py
+++ b/lib/ansible/modules/web_infrastructure/apache2_module.py
@@ -24,7 +24,10 @@
 ---
 module: apache2_module
 version_added: 1.6
-author: "Christian Berendt (@berendt)"
+author:
+    - Christian Berendt (@berendt)
+    - Ralf Hertel (@n0trax)
+    - Robin Roth (@robinro)
 short_description: enables/disables a module of the Apache2 webserver
 description:
    - Enables or disables a specified module of the Apache2 webserver.
@@ -37,15 +40,20 @@
      description:
         - force disabling of default modules and override Debian warnings
      required: false
-     choices: ['yes', 'no']
-     default: no
+     choices: ['True', 'False']
+     default: False
      version_added: "2.1"
    state:
      description:
         - indicate the desired state of the resource
      choices: ['present', 'absent']
      default: present
-
+   ignore_configcheck:
+     description:
+        - Ignore configuration checks about inconsistent module configuration. Especially for mpm_* modules.
+     choices: ['True', 'False']
+     default: False
+     version_added: "2.3"
 requirements: ["a2enmod","a2dismod"]
 '''
 
@@ -54,11 +62,43 @@
 - apache2_module:
     state: present
     name: wsgi
-
 # disables the Apache2 module "wsgi"
 - apache2_module:
     state: absent
     name: wsgi
+# disable default modules for Debian
+- apache2_module:
+    state: absent
+    name: autoindex
+    force: True
+# disable mpm_worker and ignore warnings about missing mpm module
+- apache2_module:
+    state: absent
+    name: mpm_worker
+    ignore_configcheck: True
+'''
+
+RETURN = '''
+result:
+    description: message about action taken
+    returned: always
+    type: string
+warnings:
+    description: list of warning messages
+    returned: when needed
+    type: list
+rc:
+    description: return code of underlying command
+    returned: failed
+    type: int
+stdout:
+    description: stdout of underlying command
+    returned: failed
+    type: string
+stderr:
+    description: stderr of underlying command
+    returned: failed
+    type: string
 '''
 
 import re
@@ -80,11 +120,14 @@ def _get_ctl_binary(module):
             return ctl_binary
 
     module.fail_json(
-      msg="None of httpd, apachectl or apach2ctl found. At least one apache control binary is necessary.")
+      msg="Neither of apache2ctl nor apachctl found."
+          " At least one apache control binary is necessary."
+    )
 
 def _module_is_enabled(module):
     control_binary = _get_ctl_binary(module)
     name = module.params['name']
+    ignore_configcheck = module.params['ignore_configcheck']
 
     result, stdout, stderr = module.run_command("%s -M" % control_binary)
 
@@ -95,12 +138,20 @@ def _module_is_enabled(module):
         name = "php7"
 
     if result != 0:
-        module.fail_json(msg="Error executing %s: %s" % (control_binary, stderr))
+        error_msg = "Error executing %s: %s" % (control_binary, stderr)
+        if ignore_configcheck:
+            if 'AH00534' in stderr and 'mpm_' in name:
+                module.warnings.append(
+                    "No MPM module loaded! apache2 reload AND other module actions"
+                    " will fail if no MPM module is loaded immediatly."
+                )
+            else:
+                module.warnings.append(error_msg)
+            return False
+        else:
+            module.fail_json(msg=error_msg)
 
-    if re.search(r' ' + name + r'_module', stdout):
-        return True
-    else:
-        return False
+    return bool(re.search(r' ' + name + r'_module', stdout))
 
 def _set_state(module, state):
     name = module.params['name']
@@ -113,7 +164,9 @@ def _set_state(module, state):
 
     if _module_is_enabled(module) != want_enabled:
         if module.check_mode:
-            module.exit_json(changed = True, result = success_msg)
+            module.exit_json(changed=True,
+                             result=success_msg,
+                             warnings=module.warnings)
 
         a2mod_binary = module.get_bin_path(a2mod_binary)
         if a2mod_binary is None:
@@ -126,22 +179,32 @@ def _set_state(module, state):
         result, stdout, stderr = module.run_command("%s %s" % (a2mod_binary, name))
 
         if _module_is_enabled(module) == want_enabled:
-            module.exit_json(changed = True, result = success_msg)
+            module.exit_json(changed=True,
+                             result=success_msg,
+                             warnings=module.warnings)
         else:
-            module.fail_json(msg="Failed to set module %s to %s: %s" % (name, state_string, stdout), rc=result, stdout=stdout, stderr=stderr)
+            module.fail_json(msg="Failed to set module %s to %s: %s" % (name, state_string, stdout),
+                             rc=result,
+                             stdout=stdout,
+                             stderr=stderr)
     else:
-        module.exit_json(changed = False, result = success_msg)
+        module.exit_json(changed=False,
+                         result=success_msg,
+                         warnings=module.warnings)
 
 def main():
     module = AnsibleModule(
         argument_spec = dict(
             name  = dict(required=True),
             force = dict(required=False, type='bool', default=False),
-            state = dict(default='present', choices=['absent', 'present'])
+            state = dict(default='present', choices=['absent', 'present']),
+            ignore_configcheck=dict(required=False, type='bool', default=False),
         ),
         supports_check_mode = True,
     )
 
+    module.warnings = []
+
     name = module.params['name']
     if name == 'cgi' and _run_threaded(module):
         module.fail_json(msg="Your MPM seems to be threaded. No automatic actions on module %s possible." % name)


Test patch: