prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add support for checking exit codes on shell dependencies
  # Issue Type
  
  - Feature request
  
  # Molecule and Ansible details
  
  ```
  ansible 2.10.5
    config file = /Users/jhg03a/<redacted>/ansible.cfg
    configured module search path = ['/Users/jhg03a/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
    ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
    executable location = /usr/local/bin/ansible
    python version = 3.9.2 (default, Feb 24 2021, 13:26:09) [Clang 12.0.0 (clang-1200.0.32.29)]
  molecule 3.2.3 using python 3.9
      ansible:2.10.5
      delegated:3.2.3 from molecule
      docker:0.2.4 from molecule_docker
  ```
  
  Molecule installation method (one of):
  
  - pip
  
  Ansible installation method (one of):
  
  - pip
  
  # Desired Behavior
  
  Currently it appears that the dependency shell module doesn't take into account the exit code from the command.  If something goes wrong in the dependency phase, it's highly likely the rest of the run is going to fail or have inconsistent results.
  
  ### Example:
  ```yaml
  dependency:
    name: shell
    command: 'false'
  ```
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/molecule/dependency/base.py`
  Content:
  ```
  1 #  Copyright (c) 2015-2018 Cisco Systems, Inc.
  2 #
  3 #  Permission is hereby granted, free of charge, to any person obtaining a copy
  4 #  of this software and associated documentation files (the "Software"), to
  5 #  deal in the Software without restriction, including without limitation the
  6 #  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  7 #  sell copies of the Software, and to permit persons to whom the Software is
  8 #  furnished to do so, subject to the following conditions:
  9 #
  10 #  The above copyright notice and this permission notice shall be included in
  11 #  all copies or substantial portions of the Software.
  12 #
  13 #  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 #  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 #  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 #  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 #  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  18 #  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  19 #  DEALINGS IN THE SOFTWARE.
  20 """Base Dependency Module."""
  21 
  22 import abc
  23 import logging
  24 import os
  25 import time
  26 
  27 from molecule import constants, util
  28 
  29 LOG = logging.getLogger(__name__)
  30 
  31 
  32 class Base(object):
  33     """Dependency Base Class."""
  34 
  35     __metaclass__ = abc.ABCMeta
  36 
  37     RETRY = 3
  38     SLEEP = 3
  39     BACKOFF = 3
  40 
  41     def __init__(self, config):
  42         """
  43         Initialize code for all :ref:`Dependency` classes.
  44 
  45         :param config: An instance of a Molecule config.
  46         :returns: None
  47         """
  48         self._config = config
  49 
  50     def execute_with_retries(self):
  51         """Run dependency downloads with retry and timed back-off."""
  52         exception = None
  53 
  54         try:
  55             # print(555, self._sh_command)
  56             util.run_command(self._sh_command, debug=self._config.debug)
  57             msg = "Dependency completed successfully."
  58             LOG.info(msg)
  59             return
  60         except Exception:
  61             pass
  62 
  63         for counter in range(1, (self.RETRY + 1)):
  64             msg = "Retrying dependency ... {}/{} time(s)".format(counter, self.RETRY)
  65             LOG.warning(msg)
  66 
  67             msg = "Sleeping {} seconds before retrying ...".format(self.SLEEP)
  68             LOG.warning(msg)
  69             time.sleep(self.SLEEP)
  70             self.SLEEP += self.BACKOFF
  71 
  72             try:
  73                 util.run_command(self._sh_command, debug=self._config.debug)
  74                 msg = "Dependency completed successfully."
  75                 LOG.info(msg)
  76                 return
  77             except Exception as _exception:
  78                 exception = _exception
  79 
  80         LOG.error(str(exception), self._sh_command)
  81         util.sysexit(getattr(exception, "exit_code", constants.RC_UNKNOWN_ERROR))
  82 
  83     @abc.abstractmethod
  84     def execute(self):  # pragma: no cover
  85         """
  86         Execute ``cmd`` and returns None.
  87 
  88         :return: None
  89         """
  90 
  91     @abc.abstractproperty
  92     def default_options(self):  # pragma: no cover
  93         """
  94         Get default CLI arguments provided to ``cmd`` as a dict.
  95 
  96         :return: dict
  97         """
  98 
  99     @property
  100     def default_env(self):  # pragma: no cover
  101         """
  102         Get default env variables provided to ``cmd`` as a dict.
  103 
  104         :return: dict
  105         """
  106         env = util.merge_dicts(os.environ, self._config.env)
  107         # inject ephemeral_directory on top of path
  108         env[self._config.ansible_collections_path] = os.path.join(
  109             self._config.scenario.ephemeral_directory, "collections"
  110         )
  111         return env
  112 
  113     @property
  114     def name(self):
  115         """
  116         Name of the dependency and returns a string.
  117 
  118         :returns: str
  119         """
  120         return self._config.config["dependency"]["name"]
  121 
  122     @property
  123     def enabled(self):
  124         return self._config.config["dependency"]["enabled"]
  125 
  126     @property
  127     def options(self):
  128         return util.merge_dicts(
  129             self.default_options, self._config.config["dependency"]["options"]
  130         )
  131 
  132     @property
  133     def env(self):
  134         return util.merge_dicts(
  135             self.default_env, self._config.config["dependency"]["env"]
  136         )
  137
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/molecule/dependency/base.py b/src/molecule/dependency/base.py
  --- a/src/molecule/dependency/base.py
  +++ b/src/molecule/dependency/base.py
  @@ -23,8 +23,9 @@
   import logging
   import os
   import time
  +from subprocess import CalledProcessError
   
  -from molecule import constants, util
  +from molecule import util
   
   LOG = logging.getLogger(__name__)
   
  @@ -53,11 +54,11 @@
   
           try:
               # print(555, self._sh_command)
  -            util.run_command(self._sh_command, debug=self._config.debug)
  +            util.run_command(self._sh_command, debug=self._config.debug, check=True)
               msg = "Dependency completed successfully."
               LOG.info(msg)
               return
  -        except Exception:
  +        except CalledProcessError:
               pass
   
           for counter in range(1, (self.RETRY + 1)):
  @@ -70,15 +71,15 @@
               self.SLEEP += self.BACKOFF
   
               try:
  -                util.run_command(self._sh_command, debug=self._config.debug)
  +                util.run_command(self._sh_command, debug=self._config.debug, check=True)
                   msg = "Dependency completed successfully."
                   LOG.info(msg)
                   return
  -            except Exception as _exception:
  +            except CalledProcessError as _exception:
                   exception = _exception
   
  -        LOG.error(str(exception), self._sh_command)
  -        util.sysexit(getattr(exception, "exit_code", constants.RC_UNKNOWN_ERROR))
  +        LOG.error(str(exception))
  +        util.sysexit(exception.returncode)
   
       @abc.abstractmethod
       def execute(self):  # pragma: no cover
  
verification_info: {"golden_diff": "diff --git a/src/molecule/dependency/base.py b/src/molecule/dependency/base.py\n--- a/src/molecule/dependency/base.py\n+++ b/src/molecule/dependency/base.py\n@@ -23,8 +23,9 @@\n import logging\n import os\n import time\n+from subprocess import CalledProcessError\n \n-from molecule import constants, util\n+from molecule import util\n \n LOG = logging.getLogger(__name__)\n \n@@ -53,11 +54,11 @@\n \n         try:\n             # print(555, self._sh_command)\n-            util.run_command(self._sh_command, debug=self._config.debug)\n+            util.run_command(self._sh_command, debug=self._config.debug, check=True)\n             msg = \"Dependency completed successfully.\"\n             LOG.info(msg)\n             return\n-        except Exception:\n+        except CalledProcessError:\n             pass\n \n         for counter in range(1, (self.RETRY + 1)):\n@@ -70,15 +71,15 @@\n             self.SLEEP += self.BACKOFF\n \n             try:\n-                util.run_command(self._sh_command, debug=self._config.debug)\n+                util.run_command(self._sh_command, debug=self._config.debug, check=True)\n                 msg = \"Dependency completed successfully.\"\n                 LOG.info(msg)\n                 return\n-            except Exception as _exception:\n+            except CalledProcessError as _exception:\n                 exception = _exception\n \n-        LOG.error(str(exception), self._sh_command)\n-        util.sysexit(getattr(exception, \"exit_code\", constants.RC_UNKNOWN_ERROR))\n+        LOG.error(str(exception))\n+        util.sysexit(exception.returncode)\n \n     @abc.abstractmethod\n     def execute(self):  # pragma: no cover\n", "issue": "Add support for checking exit codes on shell dependencies\n# Issue Type\r\n\r\n- Feature request\r\n\r\n# Molecule and Ansible details\r\n\r\n```\r\nansible 2.10.5\r\n  config file = /Users/jhg03a/<redacted>/ansible.cfg\r\n  configured module search path = ['/Users/jhg03a/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /usr/local/lib/python3.9/site-packages/ansible\r\n  executable location = /usr/local/bin/ansible\r\n  python version = 3.9.2 (default, Feb 24 2021, 13:26:09) [Clang 12.0.0 (clang-1200.0.32.29)]\r\nmolecule 3.2.3 using python 3.9\r\n    ansible:2.10.5\r\n    delegated:3.2.3 from molecule\r\n    docker:0.2.4 from molecule_docker\r\n```\r\n\r\nMolecule installation method (one of):\r\n\r\n- pip\r\n\r\nAnsible installation method (one of):\r\n\r\n- pip\r\n\r\n# Desired Behavior\r\n\r\nCurrently it appears that the dependency shell module doesn't take into account the exit code from the command.  If something goes wrong in the dependency phase, it's highly likely the rest of the run is going to fail or have inconsistent results.\r\n\r\n### Example:\r\n```yaml\r\ndependency:\r\n  name: shell\r\n  command: 'false'\r\n```\r\n\n", "before_files": [{"content": "#  Copyright (c) 2015-2018 Cisco Systems, Inc.\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to\n#  deal in the Software without restriction, including without limitation the\n#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n#  sell copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n#  DEALINGS IN THE SOFTWARE.\n\"\"\"Base Dependency Module.\"\"\"\n\nimport abc\nimport logging\nimport os\nimport time\n\nfrom molecule import constants, util\n\nLOG = logging.getLogger(__name__)\n\n\nclass Base(object):\n    \"\"\"Dependency Base Class.\"\"\"\n\n    __metaclass__ = abc.ABCMeta\n\n    RETRY = 3\n    SLEEP = 3\n    BACKOFF = 3\n\n    def __init__(self, config):\n        \"\"\"\n        Initialize code for all :ref:`Dependency` classes.\n\n        :param config: An instance of a Molecule config.\n        :returns: None\n        \"\"\"\n        self._config = config\n\n    def execute_with_retries(self):\n        \"\"\"Run dependency downloads with retry and timed back-off.\"\"\"\n        exception = None\n\n        try:\n            # print(555, self._sh_command)\n            util.run_command(self._sh_command, debug=self._config.debug)\n            msg = \"Dependency completed successfully.\"\n            LOG.info(msg)\n            return\n        except Exception:\n            pass\n\n        for counter in range(1, (self.RETRY + 1)):\n            msg = \"Retrying dependency ... {}/{} time(s)\".format(counter, self.RETRY)\n            LOG.warning(msg)\n\n            msg = \"Sleeping {} seconds before retrying ...\".format(self.SLEEP)\n            LOG.warning(msg)\n            time.sleep(self.SLEEP)\n            self.SLEEP += self.BACKOFF\n\n            try:\n                util.run_command(self._sh_command, debug=self._config.debug)\n                msg = \"Dependency completed successfully.\"\n                LOG.info(msg)\n                return\n            except Exception as _exception:\n                exception = _exception\n\n        LOG.error(str(exception), self._sh_command)\n        util.sysexit(getattr(exception, \"exit_code\", constants.RC_UNKNOWN_ERROR))\n\n    @abc.abstractmethod\n    def execute(self):  # pragma: no cover\n        \"\"\"\n        Execute ``cmd`` and returns None.\n\n        :return: None\n        \"\"\"\n\n    @abc.abstractproperty\n    def default_options(self):  # pragma: no cover\n        \"\"\"\n        Get default CLI arguments provided to ``cmd`` as a dict.\n\n        :return: dict\n        \"\"\"\n\n    @property\n    def default_env(self):  # pragma: no cover\n        \"\"\"\n        Get default env variables provided to ``cmd`` as a dict.\n\n        :return: dict\n        \"\"\"\n        env = util.merge_dicts(os.environ, self._config.env)\n        # inject ephemeral_directory on top of path\n        env[self._config.ansible_collections_path] = os.path.join(\n            self._config.scenario.ephemeral_directory, \"collections\"\n        )\n        return env\n\n    @property\n    def name(self):\n        \"\"\"\n        Name of the dependency and returns a string.\n\n        :returns: str\n        \"\"\"\n        return self._config.config[\"dependency\"][\"name\"]\n\n    @property\n    def enabled(self):\n        return self._config.config[\"dependency\"][\"enabled\"]\n\n    @property\n    def options(self):\n        return util.merge_dicts(\n            self.default_options, self._config.config[\"dependency\"][\"options\"]\n        )\n\n    @property\n    def env(self):\n        return util.merge_dicts(\n            self.default_env, self._config.config[\"dependency\"][\"env\"]\n        )\n", "path": "src/molecule/dependency/base.py"}], "after_files": [{"content": "#  Copyright (c) 2015-2018 Cisco Systems, Inc.\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to\n#  deal in the Software without restriction, including without limitation the\n#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n#  sell copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n#  DEALINGS IN THE SOFTWARE.\n\"\"\"Base Dependency Module.\"\"\"\n\nimport abc\nimport logging\nimport os\nimport time\nfrom subprocess import CalledProcessError\n\nfrom molecule import util\n\nLOG = logging.getLogger(__name__)\n\n\nclass Base(object):\n    \"\"\"Dependency Base Class.\"\"\"\n\n    __metaclass__ = abc.ABCMeta\n\n    RETRY = 3\n    SLEEP = 3\n    BACKOFF = 3\n\n    def __init__(self, config):\n        \"\"\"\n        Initialize code for all :ref:`Dependency` classes.\n\n        :param config: An instance of a Molecule config.\n        :returns: None\n        \"\"\"\n        self._config = config\n\n    def execute_with_retries(self):\n        \"\"\"Run dependency downloads with retry and timed back-off.\"\"\"\n        exception = None\n\n        try:\n            # print(555, self._sh_command)\n            util.run_command(self._sh_command, debug=self._config.debug, check=True)\n            msg = \"Dependency completed successfully.\"\n            LOG.info(msg)\n            return\n        except CalledProcessError:\n            pass\n\n        for counter in range(1, (self.RETRY + 1)):\n            msg = \"Retrying dependency ... {}/{} time(s)\".format(counter, self.RETRY)\n            LOG.warning(msg)\n\n            msg = \"Sleeping {} seconds before retrying ...\".format(self.SLEEP)\n            LOG.warning(msg)\n            time.sleep(self.SLEEP)\n            self.SLEEP += self.BACKOFF\n\n            try:\n                util.run_command(self._sh_command, debug=self._config.debug, check=True)\n                msg = \"Dependency completed successfully.\"\n                LOG.info(msg)\n                return\n            except CalledProcessError as _exception:\n                exception = _exception\n\n        LOG.error(str(exception))\n        util.sysexit(exception.returncode)\n\n    @abc.abstractmethod\n    def execute(self):  # pragma: no cover\n        \"\"\"\n        Execute ``cmd`` and returns None.\n\n        :return: None\n        \"\"\"\n\n    @abc.abstractproperty\n    def default_options(self):  # pragma: no cover\n        \"\"\"\n        Get default CLI arguments provided to ``cmd`` as a dict.\n\n        :return: dict\n        \"\"\"\n\n    @property\n    def default_env(self):  # pragma: no cover\n        \"\"\"\n        Get default env variables provided to ``cmd`` as a dict.\n\n        :return: dict\n        \"\"\"\n        env = util.merge_dicts(os.environ, self._config.env)\n        # inject ephemeral_directory on top of path\n        env[self._config.ansible_collections_path] = os.path.join(\n            self._config.scenario.ephemeral_directory, \"collections\"\n        )\n        return env\n\n    @property\n    def name(self):\n        \"\"\"\n        Name of the dependency and returns a string.\n\n        :returns: str\n        \"\"\"\n        return self._config.config[\"dependency\"][\"name\"]\n\n    @property\n    def enabled(self):\n        return self._config.config[\"dependency\"][\"enabled\"]\n\n    @property\n    def options(self):\n        return util.merge_dicts(\n            self.default_options, self._config.config[\"dependency\"][\"options\"]\n        )\n\n    @property\n    def env(self):\n        return util.merge_dicts(\n            self.default_env, self._config.config[\"dependency\"][\"env\"]\n        )\n", "path": "src/molecule/dependency/base.py"}]}