Repository: ansible/ansible

Issue:
elasticsearch_plugin fails when plugin_bin is None
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
elasticsearch_plugin module

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
Latest ansible devel:
ansible 2.5.0
  config file = 
  configured module search path = 
  ansible python module location = /usr/local/lib/python3.5/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.5.2 (default, Sep 14 2017, 22:51:06) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"

Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).

-->
None

##### 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.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
Ubuntu, Elasticsearch 6.x beta

##### SUMMARY
<!--- Explain the problem briefly -->
_elasticsearch_plugin_ raises `UnboundLocalError: local variable 'valid_plugin_bin' referenced before assignment` when called with default `plugin_bin` arg (which is None).

The problem lies in the `get_plugin_bin` function: it does nothing when `plugin_bin` is None, but still tries to check `valid_plugin_bin` at the end.

Applying the following patch seems to resolve the issue.
```diff
@@ -198,28 +198,27 @@ def remove_plugin(module, plugin_bin, plugin_name):
 
 def get_plugin_bin(module, plugin_bin):
     # Use the plugin_bin that was supplied first before trying other options
-    if plugin_bin:
-        if os.path.isfile(plugin_bin):
-            valid_plugin_bin = plugin_bin
-
-        else:
-            # Add the plugin_bin passed into the module to the top of the list of paths to test,
-            # testing for that binary name first before falling back to the default paths.
-            bin_paths = list(PLUGIN_BIN_PATHS)
-            if plugin_bin and plugin_bin not in bin_paths:
-                bin_paths.insert(0, plugin_bin)
-
-            # Get separate lists of dirs and binary names from the full paths to the
-            # plugin binaries.
-            plugin_dirs = list(set([os.path.dirname(x) for x in bin_paths]))
-            plugin_bins = list(set([os.path.basename(x) for x in bin_paths]))
-
-            # Check for the binary names in the default system paths as well as the path
-            # specified in the module arguments.
-            for bin_file in plugin_bins:
-                valid_plugin_bin = module.get_bin_path(bin_file, opt_dirs=plugin_dirs)
-                if valid_plugin_bin:
-                    break
+    if plugin_bin and os.path.isfile(plugin_bin):
+        valid_plugin_bin = plugin_bin
+
+    else:
+        # Add the plugin_bin passed into the module to the top of the list of paths to test,
+        # testing for that binary name first before falling back to the default paths.
+        bin_paths = list(PLUGIN_BIN_PATHS)
+        if plugin_bin and plugin_bin not in bin_paths:
+            bin_paths.insert(0, plugin_bin)
+
+        # Get separate lists of dirs and binary names from the full paths to the
+        # plugin binaries.
+        plugin_dirs = list(set([os.path.dirname(x) for x in bin_paths]))
+        plugin_bins = list(set([os.path.basename(x) for x in bin_paths]))
+
+        # Check for the binary names in the default system paths as well as the path
+        # specified in the module arguments.
+        for bin_file in plugin_bins:
+            valid_plugin_bin = module.get_bin_path(bin_file, opt_dirs=plugin_dirs)
+            if valid_plugin_bin:
+                break
 
     if not valid_plugin_bin:
         module.fail_json(msg='%s does not exist and no other valid plugin installers were found. Make sure Elasticsearch is installed.' % plugin_bin)
```

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Install Elasticsearch plugins
  elasticsearch_plugin:
    name: '{{ item }}'
    state: present
  with_items: '{{ elasticsearch_plugins }}'
  notify: restart elasticsearch
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
It would install plugins from `{{ elasticsearch_plugins }}`

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```json
{"changed": false, "failed": true, "item": "ingest-user-agent", "module_stderr": "Shared connection to ... closed.\r\n", "module_stdout": "\r\n\r\nTraceback (most recent call last):\r\n  File \"/tmp/ansible_6cufn7rd/ansible_module_elasticsearch_plugin.py\", line 276, in <module>\r\n    main()\r\n  File \"/tmp/ansible_6cufn7rd/ansible_module_elasticsearch_plugin.py\", line 258, in main\r\n    plugin_bin = get_plugin_bin(module, plugin_bin)\r\n  File \"/tmp/ansible_6cufn7rd/ansible_module_elasticsearch_plugin.py\", line 225, in get_plugin_bin\r\n    if not valid_plugin_bin:\r\nUnboundLocalError: local variable 'valid_plugin_bin' referenced before assignment\r\n1------- None\r\n", "msg": "MODULE FAILURE", "rc": 0}
```



Discussion:
cc @ThePixelDeveloper @samdoran
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/database/misc/elasticsearch_plugin.py b/lib/ansible/modules/database/misc/elasticsearch_plugin.py
--- a/lib/ansible/modules/database/misc/elasticsearch_plugin.py
+++ b/lib/ansible/modules/database/misc/elasticsearch_plugin.py
@@ -196,30 +196,30 @@ def remove_plugin(module, plugin_bin, plugin_name):
     return True, cmd, out, err
 
 
-def get_plugin_bin(module, plugin_bin):
+def get_plugin_bin(module, plugin_bin=None):
     # Use the plugin_bin that was supplied first before trying other options
-    if plugin_bin:
-        if os.path.isfile(plugin_bin):
-            valid_plugin_bin = plugin_bin
-
-        else:
-            # Add the plugin_bin passed into the module to the top of the list of paths to test,
-            # testing for that binary name first before falling back to the default paths.
-            bin_paths = list(PLUGIN_BIN_PATHS)
-            if plugin_bin and plugin_bin not in bin_paths:
-                bin_paths.insert(0, plugin_bin)
-
-            # Get separate lists of dirs and binary names from the full paths to the
-            # plugin binaries.
-            plugin_dirs = list(set([os.path.dirname(x) for x in bin_paths]))
-            plugin_bins = list(set([os.path.basename(x) for x in bin_paths]))
-
-            # Check for the binary names in the default system paths as well as the path
-            # specified in the module arguments.
-            for bin_file in plugin_bins:
-                valid_plugin_bin = module.get_bin_path(bin_file, opt_dirs=plugin_dirs)
-                if valid_plugin_bin:
-                    break
+    valid_plugin_bin = None
+    if plugin_bin and os.path.isfile(plugin_bin):
+        valid_plugin_bin = plugin_bin
+
+    else:
+        # Add the plugin_bin passed into the module to the top of the list of paths to test,
+        # testing for that binary name first before falling back to the default paths.
+        bin_paths = list(PLUGIN_BIN_PATHS)
+        if plugin_bin and plugin_bin not in bin_paths:
+            bin_paths.insert(0, plugin_bin)
+
+        # Get separate lists of dirs and binary names from the full paths to the
+        # plugin binaries.
+        plugin_dirs = list(set([os.path.dirname(x) for x in bin_paths]))
+        plugin_bins = list(set([os.path.basename(x) for x in bin_paths]))
+
+        # Check for the binary names in the default system paths as well as the path
+        # specified in the module arguments.
+        for bin_file in plugin_bins:
+            valid_plugin_bin = module.get_bin_path(bin_file, opt_dirs=plugin_dirs)
+            if valid_plugin_bin:
+                break
 
     if not valid_plugin_bin:
         module.fail_json(msg='%s does not exist and no other valid plugin installers were found. Make sure Elasticsearch is installed.' % plugin_bin)


Test patch: