We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Extra vars are not passed to the playbook anymore with converge.
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Please also check https://molecule.readthedocs.io/en/latest/faq.html --->
<!--- Please use https://groups.google.com/forum/#!forum/molecule-users for usage questions -->

# Issue Type

- Bug report

# Molecule and Ansible details

```
ansible 2.8.0
  config file = None
  configured module search path = ['/home/olcla/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0]

molecule (downgraded to 2.20 to fix but problem related to master branch)
```

Molecule installation method (one of):

- pip from git master

Ansible installation method (one of):

- pip

# Desired Behavior

When running `molecule converge -s my_scenario -- -e somevar=value` I expect molecule to pass the extra vars when running playbook.yml

# Actual Behaviour

I was trying to fix deprecations related to ansible 2.8 and had to install molecule from git master to overcome problems with testinfra. When running the above command with the latest molecule master, the extra vars are not passed to the playbook anymore. This is working as expected when downgrading to molecule 2.20.1 



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `molecule/command/converge.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 
21 import click
22 
23 from molecule import logger
24 from molecule.command import base
25 
26 LOG = logger.get_logger(__name__)
27 
28 
29 class Converge(base.Base):
30     """
31     .. program:: molecule converge
32 
33     .. option:: molecule converge
34 
35         Target the default scenario.
36 
37     .. program:: molecule converge --scenario-name foo
38 
39     .. option:: molecule converge --scenario-name foo
40 
41         Targeting a specific scenario.
42 
43     .. program:: molecule converge -- -vvv --tags foo,bar
44 
45     .. option:: molecule converge -- -vvv --tags foo,bar
46 
47         Providing additional command line arguments to the `ansible-playbook`
48         command.  Use this option with care, as there is no sanitation or
49         validation of input.  Options passed on the CLI override options
50         provided in provisioner's `options` section of `molecule.yml`.
51 
52     .. program:: molecule --debug converge
53 
54     .. option:: molecule --debug converge
55 
56         Executing with `debug`.
57 
58     .. program:: molecule --base-config base.yml converge
59 
60     .. option:: molecule --base-config base.yml converge
61 
62         Executing with a `base-config`.
63 
64     .. program:: molecule --env-file foo.yml converge
65 
66     .. option:: molecule --env-file foo.yml converge
67 
68         Load an env file to read variables from when rendering
69         molecule.yml.
70     """
71 
72     def execute(self):
73         """
74         Execute the actions necessary to perform a `molecule converge` and
75         returns None.
76 
77         :return: None
78         """
79         self.print_info()
80         self._config.provisioner.converge()
81         self._config.state.change_state('converged', True)
82 
83 
84 @click.command()
85 @click.pass_context
86 @click.option(
87     '--scenario-name',
88     '-s',
89     default=base.MOLECULE_DEFAULT_SCENARIO_NAME,
90     help='Name of the scenario to target. ({})'.format(
91         base.MOLECULE_DEFAULT_SCENARIO_NAME))
92 @click.argument('ansible_args', nargs=-1, type=click.UNPROCESSED)
93 def converge(ctx, scenario_name, ansible_args):  # pragma: no cover
94     """
95     Use the provisioner to configure instances (dependency, create, prepare
96     converge).
97     """
98 
99     args = ctx.obj.get('args')
100     subcommand = base._get_subcommand(__name__)
101     command_args = {
102         'subcommand': subcommand,
103     }
104 
105     base.execute_cmdline_scenarios(scenario_name, args, command_args)
106
```

Path: `molecule/command/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 
21 import abc
22 import collections
23 import glob
24 import os
25 
26 import six
27 
28 import molecule.command
29 import molecule.scenarios
30 from molecule import config
31 from molecule import logger
32 from molecule import util
33 
34 LOG = logger.get_logger(__name__)
35 MOLECULE_GLOB = os.environ.get('MOLECULE_GLOB', 'molecule/*/molecule.yml')
36 MOLECULE_DEFAULT_SCENARIO_NAME = 'default'
37 
38 
39 @six.add_metaclass(abc.ABCMeta)
40 class Base(object):
41     """
42     An abstract base class used to define the command interface.
43     """
44 
45     def __init__(self, c):
46         """
47         Base initializer for all :ref:`Command` classes.
48 
49         :param c: An instance of a Molecule config.
50         :returns: None
51         """
52         self._config = c
53         self._setup()
54 
55     @abc.abstractmethod
56     def execute(self):  # pragma: no cover
57         pass
58 
59     def print_info(self):
60         msg = "Scenario: '{}'".format(self._config.scenario.name)
61         LOG.info(msg)
62         msg = "Action: '{}'".format(util.underscore(self.__class__.__name__))
63         LOG.info(msg)
64 
65     def _setup(self):
66         """
67         Prepare Molecule's provisioner and returns None.
68 
69         :return: None
70         """
71         self._config.provisioner.write_config()
72         self._config.provisioner.manage_inventory()
73 
74 
75 def execute_cmdline_scenarios(scenario_name, args, command_args):
76     """
77     Execute scenario sequences based on parsed command-line arguments.
78 
79     This is useful for subcommands that run scenario sequences, which
80     excludes subcommands such as ``list``, ``login``, and ``matrix``.
81 
82     ``args`` and ``command_args`` are combined using :func:`get_configs`
83     to generate the scenario(s) configuration.
84 
85     :param scenario_name: Name of scenario to run, or ``None`` to run all.
86     :param args: ``args`` dict from ``click`` command context
87     :param command_args: dict of command argumentss, including the target
88                          subcommand to execute
89     :returns: None
90 
91     """
92     scenarios = molecule.scenarios.Scenarios(
93         get_configs(args, command_args), scenario_name)
94     scenarios.print_matrix()
95     for scenario in scenarios:
96         try:
97             execute_scenario(scenario)
98         except SystemExit:
99             # if the command has a 'destroy' arg, like test does,
100             # handle that behavior here.
101             if command_args.get('destroy') == 'always':
102                 msg = ('An error occurred during the {} sequence action: '
103                        "'{}'. Cleaning up.").format(scenario.config.subcommand,
104                                                     scenario.config.action)
105                 LOG.warning(msg)
106                 execute_subcommand(scenario.config, 'cleanup')
107                 execute_subcommand(scenario.config, 'destroy')
108                 # always prune ephemeral dir if destroying on failure
109                 scenario.prune()
110                 util.sysexit()
111             else:
112                 raise
113 
114 
115 def execute_subcommand(config, subcommand):
116     command_module = getattr(molecule.command, subcommand)
117     command = getattr(command_module, util.camelize(subcommand))
118     # knowledge of the current action is used by some provisioners
119     # to ensure they behave correctly during certain sequence steps,
120     # particulary the setting of ansible options in create/destroy,
121     # and is also used for reporting in execute_cmdline_scenarios
122     config.action = subcommand
123 
124     return command(config).execute()
125 
126 
127 def execute_scenario(scenario):
128     """
129     Execute each command in the given scenario's configured sequence.
130 
131     :param scenario: The scenario to execute.
132     :returns: None
133 
134     """
135 
136     for action in scenario.sequence:
137         execute_subcommand(scenario.config, action)
138 
139     # pruning only if a 'destroy' step was in the sequence allows for normal
140     # debugging by manually stepping through a scenario sequence
141     if 'destroy' in scenario.sequence:
142         scenario.prune()
143 
144 
145 def get_configs(args, command_args, ansible_args=()):
146     """
147     Glob the current directory for Molecule config files, instantiate config
148     objects, and returns a list.
149 
150     :param args: A dict of options, arguments and commands from the CLI.
151     :param command_args: A dict of options passed to the subcommand from
152      the CLI.
153     :param ansible_args: An optional tuple of arguments provided to the
154      `ansible-playbook` command.
155     :return: list
156     """
157     configs = [
158         config.Config(
159             molecule_file=util.abs_path(c),
160             args=args,
161             command_args=command_args,
162             ansible_args=ansible_args,
163         ) for c in glob.glob(MOLECULE_GLOB)
164     ]
165     _verify_configs(configs)
166 
167     return configs
168 
169 
170 def _verify_configs(configs):
171     """
172     Verify a Molecule config was found and returns None.
173 
174     :param configs: A list containing absolute paths to Molecule config files.
175     :return: None
176     """
177     if configs:
178         scenario_names = [c.scenario.name for c in configs]
179         for scenario_name, n in collections.Counter(scenario_names).items():
180             if n > 1:
181                 msg = ("Duplicate scenario name '{}' found.  "
182                        'Exiting.').format(scenario_name)
183                 util.sysexit_with_message(msg)
184 
185     else:
186         msg = "'{}' glob failed.  Exiting.".format(MOLECULE_GLOB)
187         util.sysexit_with_message(msg)
188 
189 
190 def _get_subcommand(string):
191     return string.split('.')[-1]
192
```
--- 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/molecule/command/base.py b/molecule/command/base.py
--- a/molecule/command/base.py
+++ b/molecule/command/base.py
@@ -72,7 +72,10 @@
         self._config.provisioner.manage_inventory()
 
 
-def execute_cmdline_scenarios(scenario_name, args, command_args):
+def execute_cmdline_scenarios(scenario_name,
+                              args,
+                              command_args,
+                              ansible_args=()):
     """
     Execute scenario sequences based on parsed command-line arguments.
 
@@ -90,7 +93,7 @@
 
     """
     scenarios = molecule.scenarios.Scenarios(
-        get_configs(args, command_args), scenario_name)
+        get_configs(args, command_args, ansible_args), scenario_name)
     scenarios.print_matrix()
     for scenario in scenarios:
         try:
diff --git a/molecule/command/converge.py b/molecule/command/converge.py
--- a/molecule/command/converge.py
+++ b/molecule/command/converge.py
@@ -102,4 +102,5 @@
         'subcommand': subcommand,
     }
 
-    base.execute_cmdline_scenarios(scenario_name, args, command_args)
+    base.execute_cmdline_scenarios(scenario_name, args, command_args,
+                                   ansible_args)

{"golden_diff": "diff --git a/molecule/command/base.py b/molecule/command/base.py\n--- a/molecule/command/base.py\n+++ b/molecule/command/base.py\n@@ -72,7 +72,10 @@\n         self._config.provisioner.manage_inventory()\n \n \n-def execute_cmdline_scenarios(scenario_name, args, command_args):\n+def execute_cmdline_scenarios(scenario_name,\n+                              args,\n+                              command_args,\n+                              ansible_args=()):\n     \"\"\"\n     Execute scenario sequences based on parsed command-line arguments.\n \n@@ -90,7 +93,7 @@\n \n     \"\"\"\n     scenarios = molecule.scenarios.Scenarios(\n-        get_configs(args, command_args), scenario_name)\n+        get_configs(args, command_args, ansible_args), scenario_name)\n     scenarios.print_matrix()\n     for scenario in scenarios:\n         try:\ndiff --git a/molecule/command/converge.py b/molecule/command/converge.py\n--- a/molecule/command/converge.py\n+++ b/molecule/command/converge.py\n@@ -102,4 +102,5 @@\n         'subcommand': subcommand,\n     }\n \n-    base.execute_cmdline_scenarios(scenario_name, args, command_args)\n+    base.execute_cmdline_scenarios(scenario_name, args, command_args,\n+                                   ansible_args)\n", "issue": "Extra vars are not passed to the playbook anymore with converge.\n<!--- Verify first that your issue is not already reported on GitHub -->\r\n<!--- Please also check https://molecule.readthedocs.io/en/latest/faq.html --->\r\n<!--- Please use https://groups.google.com/forum/#!forum/molecule-users for usage questions -->\r\n\r\n# Issue Type\r\n\r\n- Bug report\r\n\r\n# Molecule and Ansible details\r\n\r\n```\r\nansible 2.8.0\r\n  config file = None\r\n  configured module search path = ['/home/olcla/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /usr/local/lib/python3.6/dist-packages/ansible\r\n  executable location = /usr/local/bin/ansible\r\n  python version = 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0]\r\n\r\nmolecule (downgraded to 2.20 to fix but problem related to master branch)\r\n```\r\n\r\nMolecule installation method (one of):\r\n\r\n- pip from git master\r\n\r\nAnsible installation method (one of):\r\n\r\n- pip\r\n\r\n# Desired Behavior\r\n\r\nWhen running `molecule converge -s my_scenario -- -e somevar=value` I expect molecule to pass the extra vars when running playbook.yml\r\n\r\n# Actual Behaviour\r\n\r\nI was trying to fix deprecations related to ansible 2.8 and had to install molecule from git master to overcome problems with testinfra. When running the above command with the latest molecule master, the extra vars are not passed to the playbook anymore. This is working as expected when downgrading to molecule 2.20.1 \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\nimport click\n\nfrom molecule import logger\nfrom molecule.command import base\n\nLOG = logger.get_logger(__name__)\n\n\nclass Converge(base.Base):\n    \"\"\"\n    .. program:: molecule converge\n\n    .. option:: molecule converge\n\n        Target the default scenario.\n\n    .. program:: molecule converge --scenario-name foo\n\n    .. option:: molecule converge --scenario-name foo\n\n        Targeting a specific scenario.\n\n    .. program:: molecule converge -- -vvv --tags foo,bar\n\n    .. option:: molecule converge -- -vvv --tags foo,bar\n\n        Providing additional command line arguments to the `ansible-playbook`\n        command.  Use this option with care, as there is no sanitation or\n        validation of input.  Options passed on the CLI override options\n        provided in provisioner's `options` section of `molecule.yml`.\n\n    .. program:: molecule --debug converge\n\n    .. option:: molecule --debug converge\n\n        Executing with `debug`.\n\n    .. program:: molecule --base-config base.yml converge\n\n    .. option:: molecule --base-config base.yml converge\n\n        Executing with a `base-config`.\n\n    .. program:: molecule --env-file foo.yml converge\n\n    .. option:: molecule --env-file foo.yml converge\n\n        Load an env file to read variables from when rendering\n        molecule.yml.\n    \"\"\"\n\n    def execute(self):\n        \"\"\"\n        Execute the actions necessary to perform a `molecule converge` and\n        returns None.\n\n        :return: None\n        \"\"\"\n        self.print_info()\n        self._config.provisioner.converge()\n        self._config.state.change_state('converged', True)\n\n\n@click.command()\n@click.pass_context\n@click.option(\n    '--scenario-name',\n    '-s',\n    default=base.MOLECULE_DEFAULT_SCENARIO_NAME,\n    help='Name of the scenario to target. ({})'.format(\n        base.MOLECULE_DEFAULT_SCENARIO_NAME))\n@click.argument('ansible_args', nargs=-1, type=click.UNPROCESSED)\ndef converge(ctx, scenario_name, ansible_args):  # pragma: no cover\n    \"\"\"\n    Use the provisioner to configure instances (dependency, create, prepare\n    converge).\n    \"\"\"\n\n    args = ctx.obj.get('args')\n    subcommand = base._get_subcommand(__name__)\n    command_args = {\n        'subcommand': subcommand,\n    }\n\n    base.execute_cmdline_scenarios(scenario_name, args, command_args)\n", "path": "molecule/command/converge.py"}, {"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\nimport abc\nimport collections\nimport glob\nimport os\n\nimport six\n\nimport molecule.command\nimport molecule.scenarios\nfrom molecule import config\nfrom molecule import logger\nfrom molecule import util\n\nLOG = logger.get_logger(__name__)\nMOLECULE_GLOB = os.environ.get('MOLECULE_GLOB', 'molecule/*/molecule.yml')\nMOLECULE_DEFAULT_SCENARIO_NAME = 'default'\n\n\n@six.add_metaclass(abc.ABCMeta)\nclass Base(object):\n    \"\"\"\n    An abstract base class used to define the command interface.\n    \"\"\"\n\n    def __init__(self, c):\n        \"\"\"\n        Base initializer for all :ref:`Command` classes.\n\n        :param c: An instance of a Molecule config.\n        :returns: None\n        \"\"\"\n        self._config = c\n        self._setup()\n\n    @abc.abstractmethod\n    def execute(self):  # pragma: no cover\n        pass\n\n    def print_info(self):\n        msg = \"Scenario: '{}'\".format(self._config.scenario.name)\n        LOG.info(msg)\n        msg = \"Action: '{}'\".format(util.underscore(self.__class__.__name__))\n        LOG.info(msg)\n\n    def _setup(self):\n        \"\"\"\n        Prepare Molecule's provisioner and returns None.\n\n        :return: None\n        \"\"\"\n        self._config.provisioner.write_config()\n        self._config.provisioner.manage_inventory()\n\n\ndef execute_cmdline_scenarios(scenario_name, args, command_args):\n    \"\"\"\n    Execute scenario sequences based on parsed command-line arguments.\n\n    This is useful for subcommands that run scenario sequences, which\n    excludes subcommands such as ``list``, ``login``, and ``matrix``.\n\n    ``args`` and ``command_args`` are combined using :func:`get_configs`\n    to generate the scenario(s) configuration.\n\n    :param scenario_name: Name of scenario to run, or ``None`` to run all.\n    :param args: ``args`` dict from ``click`` command context\n    :param command_args: dict of command argumentss, including the target\n                         subcommand to execute\n    :returns: None\n\n    \"\"\"\n    scenarios = molecule.scenarios.Scenarios(\n        get_configs(args, command_args), scenario_name)\n    scenarios.print_matrix()\n    for scenario in scenarios:\n        try:\n            execute_scenario(scenario)\n        except SystemExit:\n            # if the command has a 'destroy' arg, like test does,\n            # handle that behavior here.\n            if command_args.get('destroy') == 'always':\n                msg = ('An error occurred during the {} sequence action: '\n                       \"'{}'. Cleaning up.\").format(scenario.config.subcommand,\n                                                    scenario.config.action)\n                LOG.warning(msg)\n                execute_subcommand(scenario.config, 'cleanup')\n                execute_subcommand(scenario.config, 'destroy')\n                # always prune ephemeral dir if destroying on failure\n                scenario.prune()\n                util.sysexit()\n            else:\n                raise\n\n\ndef execute_subcommand(config, subcommand):\n    command_module = getattr(molecule.command, subcommand)\n    command = getattr(command_module, util.camelize(subcommand))\n    # knowledge of the current action is used by some provisioners\n    # to ensure they behave correctly during certain sequence steps,\n    # particulary the setting of ansible options in create/destroy,\n    # and is also used for reporting in execute_cmdline_scenarios\n    config.action = subcommand\n\n    return command(config).execute()\n\n\ndef execute_scenario(scenario):\n    \"\"\"\n    Execute each command in the given scenario's configured sequence.\n\n    :param scenario: The scenario to execute.\n    :returns: None\n\n    \"\"\"\n\n    for action in scenario.sequence:\n        execute_subcommand(scenario.config, action)\n\n    # pruning only if a 'destroy' step was in the sequence allows for normal\n    # debugging by manually stepping through a scenario sequence\n    if 'destroy' in scenario.sequence:\n        scenario.prune()\n\n\ndef get_configs(args, command_args, ansible_args=()):\n    \"\"\"\n    Glob the current directory for Molecule config files, instantiate config\n    objects, and returns a list.\n\n    :param args: A dict of options, arguments and commands from the CLI.\n    :param command_args: A dict of options passed to the subcommand from\n     the CLI.\n    :param ansible_args: An optional tuple of arguments provided to the\n     `ansible-playbook` command.\n    :return: list\n    \"\"\"\n    configs = [\n        config.Config(\n            molecule_file=util.abs_path(c),\n            args=args,\n            command_args=command_args,\n            ansible_args=ansible_args,\n        ) for c in glob.glob(MOLECULE_GLOB)\n    ]\n    _verify_configs(configs)\n\n    return configs\n\n\ndef _verify_configs(configs):\n    \"\"\"\n    Verify a Molecule config was found and returns None.\n\n    :param configs: A list containing absolute paths to Molecule config files.\n    :return: None\n    \"\"\"\n    if configs:\n        scenario_names = [c.scenario.name for c in configs]\n        for scenario_name, n in collections.Counter(scenario_names).items():\n            if n > 1:\n                msg = (\"Duplicate scenario name '{}' found.  \"\n                       'Exiting.').format(scenario_name)\n                util.sysexit_with_message(msg)\n\n    else:\n        msg = \"'{}' glob failed.  Exiting.\".format(MOLECULE_GLOB)\n        util.sysexit_with_message(msg)\n\n\ndef _get_subcommand(string):\n    return string.split('.')[-1]\n", "path": "molecule/command/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\nimport click\n\nfrom molecule import logger\nfrom molecule.command import base\n\nLOG = logger.get_logger(__name__)\n\n\nclass Converge(base.Base):\n    \"\"\"\n    .. program:: molecule converge\n\n    .. option:: molecule converge\n\n        Target the default scenario.\n\n    .. program:: molecule converge --scenario-name foo\n\n    .. option:: molecule converge --scenario-name foo\n\n        Targeting a specific scenario.\n\n    .. program:: molecule converge -- -vvv --tags foo,bar\n\n    .. option:: molecule converge -- -vvv --tags foo,bar\n\n        Providing additional command line arguments to the `ansible-playbook`\n        command.  Use this option with care, as there is no sanitation or\n        validation of input.  Options passed on the CLI override options\n        provided in provisioner's `options` section of `molecule.yml`.\n\n    .. program:: molecule --debug converge\n\n    .. option:: molecule --debug converge\n\n        Executing with `debug`.\n\n    .. program:: molecule --base-config base.yml converge\n\n    .. option:: molecule --base-config base.yml converge\n\n        Executing with a `base-config`.\n\n    .. program:: molecule --env-file foo.yml converge\n\n    .. option:: molecule --env-file foo.yml converge\n\n        Load an env file to read variables from when rendering\n        molecule.yml.\n    \"\"\"\n\n    def execute(self):\n        \"\"\"\n        Execute the actions necessary to perform a `molecule converge` and\n        returns None.\n\n        :return: None\n        \"\"\"\n        self.print_info()\n        self._config.provisioner.converge()\n        self._config.state.change_state('converged', True)\n\n\n@click.command()\n@click.pass_context\n@click.option(\n    '--scenario-name',\n    '-s',\n    default=base.MOLECULE_DEFAULT_SCENARIO_NAME,\n    help='Name of the scenario to target. ({})'.format(\n        base.MOLECULE_DEFAULT_SCENARIO_NAME))\n@click.argument('ansible_args', nargs=-1, type=click.UNPROCESSED)\ndef converge(ctx, scenario_name, ansible_args):  # pragma: no cover\n    \"\"\"\n    Use the provisioner to configure instances (dependency, create, prepare\n    converge).\n    \"\"\"\n\n    args = ctx.obj.get('args')\n    subcommand = base._get_subcommand(__name__)\n    command_args = {\n        'subcommand': subcommand,\n    }\n\n    base.execute_cmdline_scenarios(scenario_name, args, command_args,\n                                   ansible_args)\n", "path": "molecule/command/converge.py"}, {"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\nimport abc\nimport collections\nimport glob\nimport os\n\nimport six\n\nimport molecule.command\nimport molecule.scenarios\nfrom molecule import config\nfrom molecule import logger\nfrom molecule import util\n\nLOG = logger.get_logger(__name__)\nMOLECULE_GLOB = os.environ.get('MOLECULE_GLOB', 'molecule/*/molecule.yml')\nMOLECULE_DEFAULT_SCENARIO_NAME = 'default'\n\n\n@six.add_metaclass(abc.ABCMeta)\nclass Base(object):\n    \"\"\"\n    An abstract base class used to define the command interface.\n    \"\"\"\n\n    def __init__(self, c):\n        \"\"\"\n        Base initializer for all :ref:`Command` classes.\n\n        :param c: An instance of a Molecule config.\n        :returns: None\n        \"\"\"\n        self._config = c\n        self._setup()\n\n    @abc.abstractmethod\n    def execute(self):  # pragma: no cover\n        pass\n\n    def print_info(self):\n        msg = \"Scenario: '{}'\".format(self._config.scenario.name)\n        LOG.info(msg)\n        msg = \"Action: '{}'\".format(util.underscore(self.__class__.__name__))\n        LOG.info(msg)\n\n    def _setup(self):\n        \"\"\"\n        Prepare Molecule's provisioner and returns None.\n\n        :return: None\n        \"\"\"\n        self._config.provisioner.write_config()\n        self._config.provisioner.manage_inventory()\n\n\ndef execute_cmdline_scenarios(scenario_name,\n                              args,\n                              command_args,\n                              ansible_args=()):\n    \"\"\"\n    Execute scenario sequences based on parsed command-line arguments.\n\n    This is useful for subcommands that run scenario sequences, which\n    excludes subcommands such as ``list``, ``login``, and ``matrix``.\n\n    ``args`` and ``command_args`` are combined using :func:`get_configs`\n    to generate the scenario(s) configuration.\n\n    :param scenario_name: Name of scenario to run, or ``None`` to run all.\n    :param args: ``args`` dict from ``click`` command context\n    :param command_args: dict of command argumentss, including the target\n                         subcommand to execute\n    :returns: None\n\n    \"\"\"\n    scenarios = molecule.scenarios.Scenarios(\n        get_configs(args, command_args, ansible_args), scenario_name)\n    scenarios.print_matrix()\n    for scenario in scenarios:\n        try:\n            execute_scenario(scenario)\n        except SystemExit:\n            # if the command has a 'destroy' arg, like test does,\n            # handle that behavior here.\n            if command_args.get('destroy') == 'always':\n                msg = ('An error occurred during the {} sequence action: '\n                       \"'{}'. Cleaning up.\").format(scenario.config.subcommand,\n                                                    scenario.config.action)\n                LOG.warning(msg)\n                execute_subcommand(scenario.config, 'cleanup')\n                execute_subcommand(scenario.config, 'destroy')\n                # always prune ephemeral dir if destroying on failure\n                scenario.prune()\n                util.sysexit()\n            else:\n                raise\n\n\ndef execute_subcommand(config, subcommand):\n    command_module = getattr(molecule.command, subcommand)\n    command = getattr(command_module, util.camelize(subcommand))\n    # knowledge of the current action is used by some provisioners\n    # to ensure they behave correctly during certain sequence steps,\n    # particulary the setting of ansible options in create/destroy,\n    # and is also used for reporting in execute_cmdline_scenarios\n    config.action = subcommand\n\n    return command(config).execute()\n\n\ndef execute_scenario(scenario):\n    \"\"\"\n    Execute each command in the given scenario's configured sequence.\n\n    :param scenario: The scenario to execute.\n    :returns: None\n\n    \"\"\"\n\n    for action in scenario.sequence:\n        execute_subcommand(scenario.config, action)\n\n    # pruning only if a 'destroy' step was in the sequence allows for normal\n    # debugging by manually stepping through a scenario sequence\n    if 'destroy' in scenario.sequence:\n        scenario.prune()\n\n\ndef get_configs(args, command_args, ansible_args=()):\n    \"\"\"\n    Glob the current directory for Molecule config files, instantiate config\n    objects, and returns a list.\n\n    :param args: A dict of options, arguments and commands from the CLI.\n    :param command_args: A dict of options passed to the subcommand from\n     the CLI.\n    :param ansible_args: An optional tuple of arguments provided to the\n     `ansible-playbook` command.\n    :return: list\n    \"\"\"\n    configs = [\n        config.Config(\n            molecule_file=util.abs_path(c),\n            args=args,\n            command_args=command_args,\n            ansible_args=ansible_args,\n        ) for c in glob.glob(MOLECULE_GLOB)\n    ]\n    _verify_configs(configs)\n\n    return configs\n\n\ndef _verify_configs(configs):\n    \"\"\"\n    Verify a Molecule config was found and returns None.\n\n    :param configs: A list containing absolute paths to Molecule config files.\n    :return: None\n    \"\"\"\n    if configs:\n        scenario_names = [c.scenario.name for c in configs]\n        for scenario_name, n in collections.Counter(scenario_names).items():\n            if n > 1:\n                msg = (\"Duplicate scenario name '{}' found.  \"\n                       'Exiting.').format(scenario_name)\n                util.sysexit_with_message(msg)\n\n    else:\n        msg = \"'{}' glob failed.  Exiting.\".format(MOLECULE_GLOB)\n        util.sysexit_with_message(msg)\n\n\ndef _get_subcommand(string):\n    return string.split('.')[-1]\n", "path": "molecule/command/base.py"}]}