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()

```

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

{"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"}]}