Skip to content

Commit

Permalink
Add specification of a custom cookiecutter template for role creation (
Browse files Browse the repository at this point in the history
…#1647)

* Adds vscode setting folder to gitignore

Signed-off-by: Alexis Lessard <[email protected]>

* Allows specification of custom cookiecutter template for role

The molecule and test folder are still created.
New fixtures that create a temporary template folder have also been added.
That way, tests aren't dependent on other files present in the test directory.

Signed-off-by: Alexis Lessard <[email protected]>

* Adds failsafe for absent template directory

Signed-off-by: Alexis Lessard <[email protected]>

* Adds failsafe agains invalid cookiecutter templates
If a cookiecutter exception is raised, it will trigger a sysexit with the appropriate message.
Maybe a try except is not the best way to deal with this scenario, but I havent been
able to figure out how to validate the template before the execution of cookiecutter

Signed-off-by: Alexis Lessard <[email protected]>

* Adds option to role command for a cookicutter template

Signed-off-by: Alexis Lessard <[email protected]>

* Cleans the code according to linting rules

This commit corrected those errors, as shown on travis:

./molecule/command/init/role.py:69: [E501] line too long (87 > 79 characters)
./molecule/command/init/role.py:117: [E231] missing whitespace after ','
./molecule/command/init/role.py:117: [E231] missing whitespace after ','
./molecule/command/init/role.py:117: [E231] missing whitespace after ','
./molecule/command/init/role.py:118: [E501] line too long (124 > 79 characters)
./molecule/command/init/role.py:120: [E231] missing whitespace after ','
./molecule/command/init/role.py:141: [W293] blank line contains whitespace
./test/unit/command/init/test_role.py:54: [E501] line too long (96 > 79 characters)
./test/unit/command/init/test_role.py:56: [E302] expected 2 blank lines, found 1
./test/unit/command/init/test_role.py:58: [E501] line too long (80 > 79 characters)
./test/unit/command/init/test_role.py:60: [E302] expected 2 blank lines, found 1
./test/unit/command/init/test_role.py:60: [E231] missing whitespace after ','
./test/unit/command/init/test_role.py:60: [E231] missing whitespace after ','
./test/unit/command/init/test_role.py:63: [E501] line too long (86 > 79 characters)
./test/unit/command/init/test_role.py:85: [E501] line too long (82 > 79 characters)
./test/unit/command/init/test_role.py:90: [E302] expected 2 blank lines, found 1
./test/unit/command/init/test_role.py:118: [E501] line too long (122 > 79 characters)
./test/unit/command/init/test_role.py:133: [E501] line too long (88 > 79 characters)
./test/unit/command/init/test_role.py:143: [E302] expected 2 blank lines, found 1
./test/unit/command/init/test_role.py:143: [E501] line too long (103 > 79 characters)
./test/unit/command/init/test_role.py:149: [W293] blank line contains whitespace
./test/unit/command/init/test_role.py:150: [W391] blank line at end of file

Signed-off-by: Alexis Lessard <[email protected]>

* Adds a custom role template

Signed-off-by: Alexis Lessard <[email protected]>

* Adds an invalid template dir

Signed-off-by: Alexis Lessard <[email protected]>

* Refactors tests to use fixed templates

Signed-off-by: Alexis Lessard <[email protected]>

I've discovered that some resources were available for tests, so I've added two more, which are two cookiecutter templates:
One is valid, the other one is not.

Signed-off-by: Alexis Lessard <[email protected]>

* Modifies path of incorrect_path

Since we can't use "temp_dir" as a path, apparently, the incorrect path will just be absent directory wherever we are.
Doesn't matter anyway, we want to see how the code reacts when the folder doesnt exist.

Signed-off-by: Alexis Lessard <[email protected]>

* Corrects the code style

This also deletes the _generate_template_dir function, wich is now useless.
I forgot to delete it in a previous commit.

Signed-off-by: Alexis Lessard <[email protected]>

* corrects test_role according to linting test

Signed-off-by: Alexis Lessard <[email protected]>

* Corrects a spelling mistake in the init role command description

Signed-off-by: Alexis Lessard <[email protected]>

* Adds the path of the problematic template inside the error message

Signed-off-by: Alexis Lessard <[email protected]>

Tests have also been updated, altough they could assert the error message contains
the path of the folder. Don't know how to test that yet, tough...

Signed-off-by: Alexis Lessard <[email protected]>

* Cleans code according to linting and formating rules

Signed-off-by: Alexis Lessard <[email protected]>

* Adds documentation about the template option

Signed-off-by: Alexis Lessard <[email protected]>

* Cleans the code according to linting rules

Signed-off-by: Alexis Lessard <[email protected]>

* Modify init role documentation to scenario relations

Signed-off-by: Alexis Lessard <[email protected]>

Also modifies the scenario documentation to include information about cookiecutter,
specifying that a scenario includes the default cookicutter template.

Signed-off-by: Alexis Lessard <[email protected]>

* Corrects the code style

Signed-off-by: Alexis Lessard <[email protected]>

* Removes a trailing whitespace

Signed-off-by: Alexis Lessard <[email protected]>

* Corrects code style

Signed-off-by: Alexis Lessard <[email protected]>

* Revert "Adds vscode setting folder to gitignore" and changes ``init role`` documentation

This reverts commit e306961.
It also makes a change for a change request by @wilmardo. I forgot to
sign it off, so I'm fusing those commits.

Signed-off-by: Alexis Lessard <[email protected]>

* Clarifies the origin of the molecule folder added to the template

The upstream molecule folder (the default one with init role) is added.

Signed-off-by: Alexis Lessard <[email protected]>
  • Loading branch information
AlexisLessard authored and decentral1se committed Apr 9, 2019
1 parent fa64861 commit d2f1ff2
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 10 deletions.
26 changes: 19 additions & 7 deletions molecule/command/init/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import cookiecutter.main

from molecule import logger
from molecule import util

LOG = logger.get_logger(__name__)

Expand Down Expand Up @@ -52,14 +53,20 @@ def _process_templates(self,
:return: None
"""
template_dir = self._resolve_template_dir(template_dir)
self._validate_template_dir(template_dir)

cookiecutter.main.cookiecutter(
template_dir,
extra_context=extra_context,
output_dir=output_dir,
overwrite_if_exists=overwrite,
no_input=True,
)
try:
cookiecutter.main.cookiecutter(
template_dir,
extra_context=extra_context,
output_dir=output_dir,
overwrite_if_exists=overwrite,
no_input=True,
)
except cookiecutter.exceptions.NonTemplatedInputDirException:
util.sysexit_with_message("The specified template directory (" +
str(template_dir) +
") is in an invalid format")

def _resolve_template_dir(self, template_dir):
if not os.path.isabs(template_dir):
Expand All @@ -68,3 +75,8 @@ def _resolve_template_dir(self, template_dir):
'cookiecutter', template_dir)

return template_dir

def _validate_template_dir(self, template_dir):
if not os.path.isdir(template_dir):
util.sysexit_with_message("The specified template directory (" +
str(template_dir) + ") does not exist")
30 changes: 28 additions & 2 deletions molecule/command/init/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ class Role(base.Base):
.. option:: molecule init role --role-name foo
Initialize a new role.
.. program:: molecule init role --role-name foo --template path
.. option:: molecule init role --role-name foo --template path
Initialize a new role using a local *cookiecutter* template. This
allows the customization of a role while still using the upstream
``molecule`` folder. This is similar to an
``ansible-galaxy init`` skeleton. Please refer to the ``init scenario``
command in order to generate a custom ``molecule`` scenario.
"""

def __init__(self, command_args):
Expand All @@ -61,7 +71,13 @@ def execute(self):
'Cannot create new role.').format(role_name)
util.sysexit_with_message(msg)

self._process_templates('role', self._command_args, role_directory)
template_directory = ''
if 'template' in self._command_args.keys():
template_directory = self._command_args['template']
else:
template_directory = 'role'
self._process_templates(template_directory, self._command_args,
role_directory)
scenario_base_directory = os.path.join(role_directory, role_name)
templates = [
'scenario/driver/{driver_name}'.format(**self._command_args),
Expand Down Expand Up @@ -107,8 +123,15 @@ def execute(self):
type=click.Choice(config.molecule_verifiers()),
default='testinfra',
help='Name of verifier to initialize. (testinfra)')
@click.option(
'--template',
'-t',
type=click.Path(
exists=True, dir_okay=True, readable=True, resolve_path=True),
help="Path to a cookiecutter custom template to initialize the role. "
"The upstream molecule folder will be added to this template")
def role(ctx, dependency_name, driver_name, lint_name, provisioner_name,
role_name, verifier_name): # pragma: no cover
role_name, verifier_name, template): # pragma: no cover
""" Initialize a new role for use with Molecule. """
command_args = {
'dependency_name': dependency_name,
Expand All @@ -127,5 +150,8 @@ def role(ctx, dependency_name, driver_name, lint_name, provisioner_name,
if verifier_name == 'goss':
command_args['verifier_lint_name'] = 'yamllint'

if template is not None:
command_args['template'] = template

r = Role(command_args)
r.execute()
3 changes: 2 additions & 1 deletion molecule/command/init/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class Scenario(base.Base):
.. option:: molecule init scenario --scenario-name bar --role-name foo
Initialize a new scenario.
Initialize a new scenario using a local _cookiecutter_ template. In
order to customise the role, please refer to the `init role` command.
.. program:: cd foo; molecule init scenario --scenario-name bar --role-name foo
Expand Down
4 changes: 4 additions & 0 deletions test/resources/custom_role_template/cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"role_name": "OVERRIDEN",
"scenario_name": "OVERRIDEN"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This is a custom readme that's inside the custom role template.
It is used in the unit tests of the init role command.
3 changes: 3 additions & 0 deletions test/resources/invalid_role_template/bad_format/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is a custom readme that's inside the invalid role template.
When trying to use the role command with this template, an exception should be raised,
as the name of the parent folder is wrongly written.
4 changes: 4 additions & 0 deletions test/resources/invalid_role_template/cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"role_name": "OVERRIDEN",
"scenario_name": "OVERRIDEN"
}
76 changes: 76 additions & 0 deletions test/unit/command/init/test_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,40 @@ def _instance(_command_args):
return role.Role(_command_args)


@pytest.fixture
def _resources_folder_path():
resources_folder_path = os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
'resources')
return resources_folder_path


@pytest.fixture
def custom_template_dir(_resources_folder_path):
custom_template_dir_path = os.path.join(_resources_folder_path,
'custom_role_template')
return custom_template_dir_path


@pytest.fixture
def invalid_template_dir(_resources_folder_path):
invalid_role_template_path = os.path.join(_resources_folder_path,
'invalid_role_template')
return invalid_role_template_path


@pytest.fixture
def custom_readme_content(custom_template_dir):
readme_path = os.path.join(custom_template_dir,
'{{cookiecutter.role_name}}', 'README.md')

custom_readme_content = ""
with open(readme_path, 'r') as readme:
custom_readme_content = readme.read()

return custom_readme_content


def test_execute(temp_dir, _instance, patched_logger_info,
patched_logger_success):
_instance.execute()
Expand All @@ -70,3 +104,45 @@ def test_execute_role_exists(temp_dir, _instance, patched_logger_critical):

msg = 'The directory test-role exists. Cannot create new role.'
patched_logger_critical.assert_called_once_with(msg)


def test_execute_with_custom_template(temp_dir, custom_template_dir,
custom_readme_content, _command_args):
_command_args['template'] = custom_template_dir

custom_template_instance = role.Role(_command_args)
custom_template_instance.execute()

readme_path = './test-role/README.md'
assert os.path.isfile(readme_path)
with open(readme_path, 'r') as readme:
assert readme.read() == custom_readme_content

assert os.path.isdir('./test-role/molecule/default')
assert os.path.isdir('./test-role/molecule/default/tests')


def test_execute_with_absent_template(temp_dir, _command_args,
patched_logger_critical):
incorrect_path = os.path.join("absent_template_dir")
_command_args['template'] = incorrect_path

absent_template_instance = role.Role(_command_args)
with pytest.raises(SystemExit) as e:
absent_template_instance.execute()

assert e.value.code == 1
patched_logger_critical.assert_called_once()


def test_execute_with_incorrect_template(temp_dir, invalid_template_dir,
_command_args,
patched_logger_critical):
_command_args['template'] = invalid_template_dir

invalid_template_instance = role.Role(_command_args)
with pytest.raises(SystemExit) as e:
invalid_template_instance.execute()

assert e.value.code == 1
patched_logger_critical.assert_called_once()

0 comments on commit d2f1ff2

Please sign in to comment.