Skip to content

Commit

Permalink
Modify sequences to run dependency first (#1739)
Browse files Browse the repository at this point in the history
* DRY up sequence command execution

Removes a lot of repeated boilerplate found in commands that run
scenario sequences, centralizing it in the base command module.

Aside from removing duplicated code, this should also help make it a
little easier to do introspection during sequence execution and take
actions for all sequences based on the config, as seen with the
'destroy==always' handling. That behavior could now be added to any
sequence by adding support for the `--destroy` option to the decorated
command function.

* Scenarios can now prune themselves

This decouples scenario pruning from the `destroy` command, allowing
scenarios to be pruned in any context where a scenario instance is
available.

This slightly modifies the pruning behavior. Previously this would
unconditionally prune whenever a 'destroy' sequence step was executed.
Now, `prune` is only called at the end of a sequence if that sequence
included a `destroy` sequence step. This prevents unexpected pruning
during sequence execution. As an example, users attempting to use
ansible galaxy roles in the `create` and `destroy` steps would need to
very carefully ensure the `dependency` step is run before *and after*
every destroy step. With this change, a single dependency call is
needed.

* Modify sequences to run dependency first

In sequences where a playbook is run, run the `dependency` step first so
roles can be declared in scenerio requirements and used in all playbook
steps, particularly 'create', 'destroy', and 'cleanup'.

Signed-off-by: Sean Myers <[email protected]>
  • Loading branch information
Sean Myers authored and ssbarnea committed Apr 9, 2019
1 parent 38c9bf2 commit bcbec24
Show file tree
Hide file tree
Showing 30 changed files with 339 additions and 201 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
History
*******

Unreleased
==========

* `dependency` step is now run by default before any playbook sequence step, including
`create` and `destroy`. This allows the use of roles in all sequence step playbooks.

2.20
====

Expand Down
87 changes: 63 additions & 24 deletions molecule/command/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@

import abc
import collections
import fnmatch
import glob
import os

import six

import molecule.command
import molecule.scenarios
from molecule import config
from molecule import logger
from molecule import util
Expand Down Expand Up @@ -56,29 +56,6 @@ def __init__(self, c):
def execute(self): # pragma: no cover
pass

def prune(self):
"""
Prune the ephemeral directory with the exception of safe files and
returns None.
:return: None
"""
safe_files = [
self._config.provisioner.config_file,
self._config.provisioner.inventory_file,
self._config.state.state_file,
] + self._config.driver.safe_files
files = util.os_walk(self._config.scenario.ephemeral_directory, '*')
for f in files:
if not any(sf for sf in safe_files if fnmatch.fnmatch(f, sf)):
os.remove(f)

# Remove empty directories.
for dirpath, dirs, files in os.walk(
self._config.scenario.ephemeral_directory, topdown=False):
if not dirs and not files:
os.removedirs(dirpath)

def print_info(self):
msg = "Scenario: '{}'".format(self._config.scenario.name)
LOG.info(msg)
Expand All @@ -95,13 +72,75 @@ def _setup(self):
self._config.provisioner.manage_inventory()


def execute_cmdline_scenarios(scenario_name, args, command_args):
"""
Execute scenario sequences based on parsed command-line arguments.
This is useful for subcommands that run scenario sequences, which
excludes subcommands such as ``list``, ``login``, and ``matrix``.
``args`` and ``command_args`` are combined using :func:`get_configs`
to generate the scenario(s) configuration.
:param scenario_name: Name of scenario to run, or ``None`` to run all.
:param args: ``args`` dict from ``click`` command context
:param command_args: dict of command argumentss, including the target
subcommand to execute
:returns: None
"""
scenarios = molecule.scenarios.Scenarios(
get_configs(args, command_args), scenario_name)
scenarios.print_matrix()
for scenario in scenarios:
try:
execute_scenario(scenario)
except SystemExit:
# if the command has a 'destroy' arg, like test does,
# handle that behavior here.
if command_args.get('destroy') == 'always':
msg = ('An error occurred during the {} sequence action: '
"'{}'. Cleaning up.").format(scenario.config.subcommand,
scenario.config.action)
LOG.warn(msg)
execute_subcommand(scenario.config, 'cleanup')
execute_subcommand(scenario.config, 'destroy')
# always prune ephemeral dir if destroying on failure
scenario.prune()
util.sysexit()
else:
raise


def execute_subcommand(config, subcommand):
command_module = getattr(molecule.command, subcommand)
command = getattr(command_module, util.camelize(subcommand))

return command(config).execute()


def execute_scenario(scenario):
"""
Execute each command in the given scenario's configured sequence.
:param scenario: The scenario to execute.
:returns: None
"""

for action in scenario.sequence:
# knowledge of the current action is used by some provisioners
# to ensure they behave correctly during certain sequence steps,
# and is also used for reporting in execute_cmdline_scenarios
scenario.config.action = action
execute_subcommand(scenario.config, action)

# pruning only if a 'destroy' step was in the sequence allows for normal
# debugging by manually stepping through a scenario sequence
if 'destroy' in scenario.sequence:
scenario.prune()


def get_configs(args, command_args, ansible_args=()):
"""
Glob the current directory for Molecule config files, instantiate config
Expand Down
9 changes: 1 addition & 8 deletions molecule/command/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -91,10 +90,4 @@ def check(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -100,10 +99,4 @@ def cleanup(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/converge.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -103,10 +102,4 @@ def converge(ctx, scenario_name, ansible_args): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args, ansible_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from molecule import config
from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -115,10 +114,4 @@ def create(ctx, scenario_name, driver_name): # pragma: no cover
'driver_name': driver_name,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -88,10 +87,4 @@ def dependency(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
10 changes: 1 addition & 9 deletions molecule/command/destroy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from molecule import config
from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -95,7 +94,6 @@ def execute(self):

self._config.provisioner.destroy()
self._config.state.reset()
self.prune()


@click.command()
Expand Down Expand Up @@ -128,10 +126,4 @@ def destroy(ctx, scenario_name, driver_name, __all): # pragma: no cover
if __all:
scenario_name = None

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/idempotence.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule import util
from molecule.command import base

Expand Down Expand Up @@ -157,10 +156,4 @@ def idempotence(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -97,10 +96,4 @@ def lint(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from molecule import config
from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -134,10 +133,4 @@ def prepare(ctx, scenario_name, driver_name, force): # pragma: no cover
'force': force,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/side_effect.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -96,10 +95,4 @@ def side_effect(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
9 changes: 1 addition & 8 deletions molecule/command/syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import click

from molecule import logger
from molecule import scenarios
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -88,10 +87,4 @@ def syntax(ctx, scenario_name): # pragma: no cover
'subcommand': subcommand,
}

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
base.execute_cmdline_scenarios(scenario_name, args, command_args)
20 changes: 1 addition & 19 deletions molecule/command/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@

from molecule import config
from molecule import logger
from molecule import scenarios
from molecule import util
from molecule.command import base

LOG = logger.get_logger(__name__)
Expand Down Expand Up @@ -125,20 +123,4 @@ def test(ctx, scenario_name, driver_name, __all, destroy): # pragma: no cover
if __all:
scenario_name = None

s = scenarios.Scenarios(
base.get_configs(args, command_args), scenario_name)
s.print_matrix()
for scenario in s:
try:
for action in scenario.sequence:
scenario.config.action = action
base.execute_subcommand(scenario.config, action)
except SystemExit:
if destroy == 'always':
msg = ('An error occurred during the test sequence '
"action: '{}'. Cleaning up.").format(action)
LOG.warn(msg)
base.execute_subcommand(scenario.config, 'cleanup')
base.execute_subcommand(scenario.config, 'destroy')
util.sysexit()
raise
base.execute_cmdline_scenarios(scenario_name, args, command_args)
Loading

0 comments on commit bcbec24

Please sign in to comment.