Enable minimal testing
This commit is contained in:
0
molecule_hetznercloud/test/__init__.py
Normal file
0
molecule_hetznercloud/test/__init__.py
Normal file
104
molecule_hetznercloud/test/conftest.py
Normal file
104
molecule_hetznercloud/test/conftest.py
Normal file
@ -0,0 +1,104 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
import pytest
|
||||
from molecule import config, logger, util
|
||||
from molecule.scenario import ephemeral_directory
|
||||
|
||||
LOG = logger.get_logger(__name__)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def run_command(cmd, env=os.environ, log=True):
|
||||
if log:
|
||||
cmd = _rebake_command(cmd, env)
|
||||
cmd = cmd.bake(_truncate_exc=False)
|
||||
return util.run_command(cmd)
|
||||
|
||||
|
||||
def _rebake_command(cmd, env, out=LOG.out, err=LOG.error):
|
||||
return cmd.bake(_env=env, _out=out, _err=err)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def random_string(length=5):
|
||||
return "".join((random.choice(string.ascii_uppercase) for _ in range(length)))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def change_dir_to(dir_name):
|
||||
cwd = os.getcwd()
|
||||
os.chdir(dir_name)
|
||||
yield
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir(tmpdir, random_string, request):
|
||||
directory = tmpdir.mkdir(random_string)
|
||||
|
||||
with change_dir_to(directory.strpath):
|
||||
yield directory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resources_folder_path():
|
||||
resources_folder_path = os.path.join(os.path.dirname(__file__), "resources")
|
||||
return resources_folder_path
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def molecule_project_directory():
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def molecule_directory():
|
||||
return config.molecule_directory(molecule_project_directory())
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def molecule_scenario_directory():
|
||||
return os.path.join(molecule_directory(), "default")
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def molecule_file():
|
||||
return get_molecule_file(molecule_scenario_directory())
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def get_molecule_file(path):
|
||||
return config.molecule_file(path)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def molecule_ephemeral_directory(_fixture_uuid):
|
||||
project_directory = "test-project-{}".format(_fixture_uuid)
|
||||
scenario_name = "test-instance"
|
||||
|
||||
return ephemeral_directory(
|
||||
os.path.join("molecule_test", project_directory, scenario_name)
|
||||
)
|
9
molecule_hetznercloud/test/functional/.ansible-lint
Normal file
9
molecule_hetznercloud/test/functional/.ansible-lint
Normal file
@ -0,0 +1,9 @@
|
||||
# ansible-lint config for functional testing, used to bypass expected metadata
|
||||
# errors in molecule-generated roles. Loaded via the metadata_lint_update
|
||||
# pytest helper. For reference, see "E7xx - metadata" in:
|
||||
# https://docs.ansible.com/ansible-lint/rules/default_rules.html
|
||||
skip_list:
|
||||
# metadata/701 - Role info should contain platforms
|
||||
- '701'
|
||||
# metadata/703 - Should change default metadata: <field>"
|
||||
- '703'
|
0
molecule_hetznercloud/test/functional/__init__.py
Normal file
0
molecule_hetznercloud/test/functional/__init__.py
Normal file
246
molecule_hetznercloud/test/functional/conftest.py
Normal file
246
molecule_hetznercloud/test/functional/conftest.py
Normal file
@ -0,0 +1,246 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
# Copyright (c) 2018 Red Hat, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import os
|
||||
import pkg_resources
|
||||
import shutil
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
import sh
|
||||
|
||||
from molecule import logger
|
||||
from molecule import util
|
||||
|
||||
from ..conftest import change_dir_to
|
||||
|
||||
LOG = logger.get_logger(__name__)
|
||||
|
||||
IS_TRAVIS = os.getenv("TRAVIS") and os.getenv("CI")
|
||||
|
||||
|
||||
def _env_vars_exposed(env_vars, env=os.environ):
|
||||
"""Check if environment variables are exposed and populated."""
|
||||
for env_var in env_vars:
|
||||
if env_var not in os.environ:
|
||||
return False
|
||||
return os.environ[env_var] != ""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_scenario(request, scenario_to_test, driver_name, scenario_name, skip_test):
|
||||
scenario_directory = os.path.join(
|
||||
os.path.dirname(util.abs_path(__file__)),
|
||||
os.path.pardir,
|
||||
"scenarios",
|
||||
scenario_to_test,
|
||||
)
|
||||
|
||||
with change_dir_to(scenario_directory):
|
||||
yield
|
||||
if scenario_name:
|
||||
msg = "CLEANUP: Destroying instances for all scenario(s)"
|
||||
LOG.out(msg)
|
||||
options = {"driver_name": driver_name, "all": True}
|
||||
cmd = sh.molecule.bake("destroy", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def skip_test(request, driver_name):
|
||||
msg_tmpl = (
|
||||
"Ignoring '{}' tests for now"
|
||||
if driver_name == "delegated"
|
||||
else "Skipped '{}' not supported"
|
||||
)
|
||||
support_checks_map = {
|
||||
"hetznercloud": lambda: min_ansible("2.8") and supports_hetznercloud()
|
||||
}
|
||||
try:
|
||||
check_func = support_checks_map[driver_name]
|
||||
if not check_func():
|
||||
pytest.skip(msg_tmpl.format(driver_name))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def idempotence(scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("create", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("converge", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("idempotence", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def init_role(temp_dir, driver_name):
|
||||
role_directory = os.path.join(temp_dir.strpath, "test-init")
|
||||
|
||||
cmd = sh.molecule.bake(
|
||||
"init", "role", {"driver-name": driver_name, "role-name": "test-init"}
|
||||
)
|
||||
pytest.helpers.run_command(cmd)
|
||||
pytest.helpers.metadata_lint_update(role_directory)
|
||||
|
||||
with change_dir_to(role_directory):
|
||||
options = {"all": True}
|
||||
cmd = sh.molecule.bake("test", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def init_scenario(temp_dir, driver_name):
|
||||
role_directory = os.path.join(temp_dir.strpath, "test-init")
|
||||
cmd = sh.molecule.bake(
|
||||
"init", "role", {"driver-name": driver_name, "role-name": "test-init"}
|
||||
)
|
||||
pytest.helpers.run_command(cmd)
|
||||
pytest.helpers.metadata_lint_update(role_directory)
|
||||
|
||||
with change_dir_to(role_directory):
|
||||
molecule_directory = pytest.helpers.molecule_directory()
|
||||
scenario_directory = os.path.join(molecule_directory, "test-scenario")
|
||||
|
||||
options = {"scenario_name": "test-scenario", "role_name": "test-init"}
|
||||
cmd = sh.molecule.bake("init", "scenario", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
assert os.path.isdir(scenario_directory)
|
||||
|
||||
options = {"scenario_name": "test-scenario", "all": True}
|
||||
cmd = sh.molecule.bake("test", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def metadata_lint_update(role_directory):
|
||||
ansible_lint_src = os.path.join(
|
||||
os.path.dirname(util.abs_path(__file__)), ".ansible-lint"
|
||||
)
|
||||
|
||||
shutil.copy(ansible_lint_src, role_directory)
|
||||
|
||||
with change_dir_to(role_directory):
|
||||
cmd = sh.ansible_lint.bake(".")
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def list(x):
|
||||
cmd = sh.molecule.bake("list")
|
||||
out = pytest.helpers.run_command(cmd, log=False)
|
||||
out = out.stdout.decode("utf-8")
|
||||
out = util.strip_ansi_color(out)
|
||||
|
||||
for l in x.splitlines():
|
||||
assert l in out
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def list_with_format_plain(x):
|
||||
cmd = sh.molecule.bake("list", {"format": "plain"})
|
||||
out = pytest.helpers.run_command(cmd, log=False)
|
||||
out = out.stdout.decode("utf-8")
|
||||
out = util.strip_ansi_color(out)
|
||||
|
||||
for l in x.splitlines():
|
||||
assert l in out
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def login(login_args, scenario_name="default"):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("destroy", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("create", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
for instance, regexp in login_args:
|
||||
if len(login_args) > 1:
|
||||
child_cmd = "molecule login --host {} --scenario-name {}".format(
|
||||
instance, scenario_name
|
||||
)
|
||||
else:
|
||||
child_cmd = "molecule login --scenario-name {}".format(scenario_name)
|
||||
child = pexpect.spawn(child_cmd)
|
||||
child.expect(regexp)
|
||||
child.sendline("exit")
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def test(driver_name, scenario_name="default", parallel=False):
|
||||
options = {
|
||||
"scenario_name": scenario_name,
|
||||
"all": scenario_name is None,
|
||||
"parallel": parallel,
|
||||
}
|
||||
|
||||
if driver_name == "delegated":
|
||||
options = {"scenario_name": scenario_name}
|
||||
|
||||
cmd = sh.molecule.bake("test", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def verify(scenario_name="default"):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("create", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("converge", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("verify", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
def min_ansible(version):
|
||||
"""Ensure current Ansible is newer than a given a minimal one."""
|
||||
try:
|
||||
from ansible.release import __version__
|
||||
|
||||
return pkg_resources.parse_version(__version__) >= pkg_resources.parse_version(
|
||||
version
|
||||
)
|
||||
except ImportError as exception:
|
||||
LOG.error("Unable to parse Ansible version", exc_info=exception)
|
||||
return False
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def supports_hetznercloud():
|
||||
pytest.importorskip("hcloud")
|
||||
|
||||
env_vars = ("HCLOUD_TOKEN",)
|
||||
|
||||
return _env_vars_exposed(env_vars)
|
312
molecule_hetznercloud/test/functional/test_command.py
Normal file
312
molecule_hetznercloud/test/functional/test_command.py
Normal file
@ -0,0 +1,312 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import sh
|
||||
from molecule.scenario import ephemeral_directory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scenario_to_test(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scenario_name(request):
|
||||
try:
|
||||
return request.param
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def driver_name(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_check(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("check", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_cleanup(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("cleanup", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_converge(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("converge", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_create(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("create", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason="Disabled due to https://github.com/ansible/galaxy/issues/2030"
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("dependency", "hetznercloud", "ansible-galaxy")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_dependency_ansible_galaxy(
|
||||
request, scenario_to_test, with_scenario, scenario_name
|
||||
):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("dependency", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
dependency_role = os.path.join(
|
||||
ephemeral_directory("molecule"),
|
||||
"dependency",
|
||||
"ansible-galaxy",
|
||||
"roles",
|
||||
"timezone",
|
||||
)
|
||||
assert os.path.isdir(dependency_role)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("dependency", "hetznercloud", "gilt")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_dependency_gilt(
|
||||
request, scenario_to_test, with_scenario, scenario_name
|
||||
):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("dependency", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
dependency_role = os.path.join(
|
||||
ephemeral_directory("molecule"), "dependency", "gilt", "roles", "timezone"
|
||||
)
|
||||
assert os.path.isdir(dependency_role)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("dependency", "hetznercloud", "shell")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_dependency_shell(
|
||||
request, scenario_to_test, with_scenario, scenario_name
|
||||
):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("dependency", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
dependency_role = os.path.join(
|
||||
ephemeral_directory("molecule"), "dependency", "shell", "roles", "timezone"
|
||||
)
|
||||
assert os.path.isdir(dependency_role)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_destroy(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("destroy", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_idempotence(scenario_to_test, with_scenario, scenario_name):
|
||||
pytest.helpers.idempotence(scenario_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"driver_name",
|
||||
[
|
||||
("digitalocean"),
|
||||
("docker"),
|
||||
("ec2"),
|
||||
("gce"),
|
||||
("linode"),
|
||||
("openstack"),
|
||||
("vagrant"),
|
||||
],
|
||||
indirect=["driver_name"],
|
||||
)
|
||||
def test_command_init_role(temp_dir, driver_name, skip_test):
|
||||
pytest.helpers.init_role(temp_dir, driver_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("driver_name", [("hetznercloud")], indirect=["driver_name"])
|
||||
def test_command_init_scenario(temp_dir, driver_name, skip_test):
|
||||
pytest.helpers.init_scenario(temp_dir, driver_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_lint(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("lint", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, expected",
|
||||
[
|
||||
(
|
||||
"driver/hetznercloud",
|
||||
"hetznercloud",
|
||||
"""
|
||||
Instance Name Driver Name Provisioner Name Scenario Name Created Converged
|
||||
--------------- ------------- ------------------ --------------- --------- -----------
|
||||
instance hetznercloud ansible default false false
|
||||
instance-1 hetznercloud ansible multi-node false false
|
||||
instance-2 hetznercloud ansible multi-node false false
|
||||
""".strip(), # noqa
|
||||
)
|
||||
],
|
||||
indirect=["scenario_to_test", "driver_name"],
|
||||
)
|
||||
def test_command_list(scenario_to_test, with_scenario, expected):
|
||||
pytest.helpers.list(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, expected",
|
||||
[
|
||||
(
|
||||
"driver/hetznercloud",
|
||||
"hetznercloud",
|
||||
"""
|
||||
instance hetznercloud ansible default false false
|
||||
instance-1 hetznercloud ansible multi-node false false
|
||||
instance-2 hetznercloud ansible multi-node false false
|
||||
""".strip(),
|
||||
) # noqa
|
||||
],
|
||||
indirect=["scenario_to_test", "driver_name"],
|
||||
)
|
||||
def test_command_list_with_format_plain(scenario_to_test, with_scenario, expected):
|
||||
pytest.helpers.list_with_format_plain(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, login_args, scenario_name",
|
||||
[
|
||||
(
|
||||
"driver/hetznercloud",
|
||||
"hetznercloud",
|
||||
[["instance-1", ".*instance-1.*"], ["instance-2", ".*instance-2.*"]],
|
||||
"multi-node",
|
||||
)
|
||||
],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_login(scenario_to_test, with_scenario, login_args, scenario_name):
|
||||
pytest.helpers.login(login_args, scenario_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_prepare(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
|
||||
cmd = sh.molecule.bake("create", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
cmd = sh.molecule.bake("prepare", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_side_effect(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("side-effect", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_syntax(scenario_to_test, with_scenario, scenario_name):
|
||||
options = {"scenario_name": scenario_name}
|
||||
cmd = sh.molecule.bake("syntax", **options)
|
||||
pytest.helpers.run_command(cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", None)],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_test(scenario_to_test, with_scenario, scenario_name, driver_name):
|
||||
pytest.helpers.test(driver_name, scenario_name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_to_test, driver_name, scenario_name",
|
||||
[("driver/hetznercloud", "hetznercloud", "default")],
|
||||
indirect=["scenario_to_test", "driver_name", "scenario_name"],
|
||||
)
|
||||
def test_command_verify(scenario_to_test, with_scenario, scenario_name):
|
||||
pytest.helpers.verify(scenario_name)
|
13
molecule_hetznercloud/test/resources/.yamllint
Normal file
13
molecule_hetznercloud/test/resources/.yamllint
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
level: error
|
||||
brackets:
|
||||
max-spaces-inside: 1
|
||||
level: error
|
||||
line-length: disable
|
||||
truthy: disable
|
20
molecule_hetznercloud/test/resources/molecule_docker.yml
Normal file
20
molecule_hetznercloud/test/resources/molecule_docker.yml
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
driver:
|
||||
name: docker
|
||||
lint:
|
||||
name: yamllint
|
||||
platforms:
|
||||
- name: instance
|
||||
image: centos:${TEST_CENTOS_VERSION}
|
||||
provisioner:
|
||||
name: ansible
|
||||
lint:
|
||||
name: ansible-lint
|
||||
scenario:
|
||||
name: ansible-galaxy
|
||||
verifier:
|
||||
name: testinfra
|
||||
lint:
|
||||
name: flake8
|
@ -0,0 +1,88 @@
|
||||
---
|
||||
- name: Create
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
no_log: "{{ molecule_no_log }}"
|
||||
vars:
|
||||
ssh_port: 22
|
||||
ssh_user: root
|
||||
ssh_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/ssh_key"
|
||||
tasks:
|
||||
- name: Create SSH key
|
||||
user:
|
||||
name: "{{ lookup('env', 'USER') }}"
|
||||
ssh_key_file: "{{ ssh_path }}"
|
||||
generate_ssh_key: true
|
||||
force: true
|
||||
register: generated_ssh_key
|
||||
|
||||
- name: Register the SSH key name
|
||||
set_fact:
|
||||
ssh_key_name: "molecule-generated-{{ 12345 | random | to_uuid }}"
|
||||
|
||||
- name: Register SSH key for test instance(s)
|
||||
hcloud_ssh_key:
|
||||
name: "{{ ssh_key_name }}"
|
||||
public_key: "{{ generated_ssh_key.ssh_public_key }}"
|
||||
state: present
|
||||
|
||||
- name: Create molecule instance(s)
|
||||
hcloud_server:
|
||||
name: "{{ item.name }}"
|
||||
server_type: "{{ item.server_type }}"
|
||||
ssh_keys:
|
||||
- "{{ ssh_key_name }}"
|
||||
volumes: "{{ item.volumes | default(omit) }}"
|
||||
image: "{{ item.image }}"
|
||||
location: "{{ item.location | default(omit) }}"
|
||||
datacenter: "{{ item.datacenter | default(omit) }}"
|
||||
user_data: "{{ item.user_data | default(omit) }}"
|
||||
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
|
||||
state: present
|
||||
register: server
|
||||
with_items: "{{ molecule_yml.platforms }}"
|
||||
async: 7200
|
||||
poll: 0
|
||||
|
||||
- name: Wait for instance(s) creation to complete
|
||||
async_status:
|
||||
jid: "{{ item.ansible_job_id }}"
|
||||
register: hetzner_jobs
|
||||
until: hetzner_jobs.finished
|
||||
retries: 300
|
||||
with_items: "{{ server.results }}"
|
||||
|
||||
# Mandatory configuration for Molecule to function.
|
||||
|
||||
- name: Populate instance config dict
|
||||
set_fact:
|
||||
instance_conf_dict: {
|
||||
'instance': "{{ item.hcloud_server.name }}",
|
||||
'ssh_key_name': "{{ ssh_key_name }}",
|
||||
'address': "{{ item.hcloud_server.ipv4_address }}",
|
||||
'user': "{{ ssh_user }}",
|
||||
'port': "{{ ssh_port }}",
|
||||
'identity_file': "{{ ssh_path }}", }
|
||||
with_items: "{{ hetzner_jobs.results }}"
|
||||
register: instance_config_dict
|
||||
when: server.changed | bool
|
||||
|
||||
- name: Convert instance config dict to a list
|
||||
set_fact:
|
||||
instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"
|
||||
when: server.changed | bool
|
||||
|
||||
- name: Dump instance config
|
||||
copy:
|
||||
content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}"
|
||||
dest: "{{ molecule_instance_config }}"
|
||||
when: server.changed | bool
|
||||
|
||||
- name: Wait for SSH
|
||||
wait_for:
|
||||
port: "{{ ssh_port }}"
|
||||
host: "{{ item.address }}"
|
||||
search_regex: SSH
|
||||
delay: 10
|
||||
with_items: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}"
|
@ -0,0 +1,57 @@
|
||||
---
|
||||
- name: Destroy
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
no_log: "{{ molecule_no_log }}"
|
||||
tasks:
|
||||
- name: Populate the instance config
|
||||
block:
|
||||
- name: Populate instance config from file
|
||||
set_fact:
|
||||
instance_conf: "{{ lookup('file', molecule_instance_config) | molecule_from_yaml }}"
|
||||
skip_instances: false
|
||||
rescue:
|
||||
- name: Populate instance config when file missing
|
||||
set_fact:
|
||||
instance_conf: {}
|
||||
skip_instances: true
|
||||
|
||||
- name: Destroy molecule instance(s)
|
||||
hcloud_server:
|
||||
name: "{{ item.instance }}"
|
||||
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
|
||||
state: absent
|
||||
register: server
|
||||
with_items: "{{ instance_conf }}"
|
||||
when: not skip_instances
|
||||
async: 7200
|
||||
poll: 0
|
||||
|
||||
- name: Wait for instance(s) deletion to complete
|
||||
async_status:
|
||||
jid: "{{ item.ansible_job_id }}"
|
||||
register: hetzner_jobs
|
||||
until: hetzner_jobs.finished
|
||||
retries: 300
|
||||
with_items: "{{ server.results }}"
|
||||
|
||||
- name: Remove registered SSH key
|
||||
hcloud_ssh_key:
|
||||
name: "{{ instance_conf[0].ssh_key_name }}"
|
||||
state: absent
|
||||
when:
|
||||
- not skip_instances
|
||||
- instance_conf # must contain at least one instance
|
||||
|
||||
# Mandatory configuration for Molecule to function.
|
||||
|
||||
- name: Populate instance config
|
||||
set_fact:
|
||||
instance_conf: {}
|
||||
|
||||
- name: Dump instance config
|
||||
copy:
|
||||
content: "{{ instance_conf | molecule_to_yaml | molecule_header }}"
|
||||
dest: "{{ molecule_instance_config }}"
|
||||
when: server.changed | bool
|
@ -0,0 +1,40 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
|
||||
driver:
|
||||
name: hetznercloud
|
||||
|
||||
lint:
|
||||
name: yamllint
|
||||
options:
|
||||
config-file: ../../../resources/.yamllint
|
||||
|
||||
platforms:
|
||||
- name: instance
|
||||
server_type: cx11
|
||||
image: debian-9
|
||||
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
create: ../../../../../resources/playbooks/hetznercloud/create.yml
|
||||
destroy: ../../../../../resources/playbooks/hetznercloud/destroy.yml
|
||||
env:
|
||||
ANSIBLE_ROLES_PATH: ../../../../../resources/roles/
|
||||
lint:
|
||||
name: ansible-lint
|
||||
config_options:
|
||||
defaults:
|
||||
timeout: 100
|
||||
ssh_connection:
|
||||
scp_if_ssh: true
|
||||
|
||||
scenario:
|
||||
name: default
|
||||
|
||||
verifier:
|
||||
name: testinfra
|
||||
lint:
|
||||
name: flake8
|
||||
enabled: false
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
roles:
|
||||
- molecule
|
@ -0,0 +1,50 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
|
||||
driver:
|
||||
name: hetznercloud
|
||||
|
||||
lint:
|
||||
name: yamllint
|
||||
options:
|
||||
config-file: ../../../resources/.yamllint
|
||||
|
||||
platforms:
|
||||
- name: instance-1
|
||||
server_type: cx11
|
||||
image: debian-9
|
||||
groups:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
- name: instance-2
|
||||
server_type: cx11
|
||||
image: debian-9
|
||||
groups:
|
||||
- foo
|
||||
- baz
|
||||
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
create: ../../../../../resources/playbooks/hetznercloud/create.yml
|
||||
destroy: ../../../../../resources/playbooks/hetznercloud/destroy.yml
|
||||
env:
|
||||
ANSIBLE_ROLES_PATH: ../../../../../resources/roles/
|
||||
lint:
|
||||
name: ansible-lint
|
||||
config_options:
|
||||
defaults:
|
||||
timeout: 100
|
||||
ssh_connection:
|
||||
scp_if_ssh: true
|
||||
|
||||
scenario:
|
||||
name: multi-node
|
||||
|
||||
verifier:
|
||||
name: testinfra
|
||||
lint:
|
||||
name: flake8
|
||||
enabled: false
|
@ -0,0 +1,24 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
roles:
|
||||
- molecule
|
||||
|
||||
- name: Converge
|
||||
hosts: bar
|
||||
gather_facts: false
|
||||
roles:
|
||||
- molecule
|
||||
|
||||
- name: Converge
|
||||
hosts: foo
|
||||
gather_facts: false
|
||||
roles:
|
||||
- molecule
|
||||
|
||||
- name: Converge
|
||||
hosts: baz
|
||||
gather_facts: false
|
||||
roles:
|
||||
- molecule
|
0
molecule_hetznercloud/test/unit/__init__.py
Normal file
0
molecule_hetznercloud/test/unit/__init__.py
Normal file
252
molecule_hetznercloud/test/unit/conftest.py
Normal file
252
molecule_hetznercloud/test/unit/conftest.py
Normal file
@ -0,0 +1,252 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from uuid import uuid4
|
||||
import copy
|
||||
import functools
|
||||
import os
|
||||
import shutil
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from molecule import util
|
||||
from molecule import config
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def write_molecule_file(filename, data):
|
||||
util.write_file(filename, util.safe_dump(data))
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def os_split(s):
|
||||
rest, tail = os.path.split(s)
|
||||
if rest in ("", os.path.sep):
|
||||
return (tail,)
|
||||
return os_split(rest) + (tail,)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_dependency_galaxy_section_data():
|
||||
return {"dependency": {"name": "galaxy"}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_driver_section_data():
|
||||
return {"driver": {"name": "docker", "options": {"managed": True}}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_lint_section_data():
|
||||
return {"lint": {"name": "yamllint"}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_platforms_section_data():
|
||||
return {
|
||||
"platforms": [
|
||||
{"name": "instance-1", "groups": ["foo", "bar"], "children": ["child1"]},
|
||||
{"name": "instance-2", "groups": ["baz", "foo"], "children": ["child2"]},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_provisioner_section_data():
|
||||
return {
|
||||
"provisioner": {
|
||||
"name": "ansible",
|
||||
"options": {"become": True},
|
||||
"lint": {"name": "ansible-lint"},
|
||||
"config_options": {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_scenario_section_data():
|
||||
return {"scenario": {"name": "default"}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_verifier_section_data():
|
||||
return {"verifier": {"name": "testinfra", "lint": {"name": "flake8"}}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def molecule_data(
|
||||
_molecule_dependency_galaxy_section_data,
|
||||
_molecule_driver_section_data,
|
||||
_molecule_lint_section_data,
|
||||
_molecule_platforms_section_data,
|
||||
_molecule_provisioner_section_data,
|
||||
_molecule_scenario_section_data,
|
||||
_molecule_verifier_section_data,
|
||||
):
|
||||
|
||||
fixtures = [
|
||||
_molecule_dependency_galaxy_section_data,
|
||||
_molecule_driver_section_data,
|
||||
_molecule_lint_section_data,
|
||||
_molecule_platforms_section_data,
|
||||
_molecule_provisioner_section_data,
|
||||
_molecule_scenario_section_data,
|
||||
_molecule_verifier_section_data,
|
||||
]
|
||||
|
||||
return functools.reduce(lambda x, y: util.merge_dicts(x, y), fixtures)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def molecule_directory_fixture(temp_dir):
|
||||
return pytest.helpers.molecule_directory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def molecule_scenario_directory_fixture(molecule_directory_fixture):
|
||||
path = pytest.helpers.molecule_scenario_directory()
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def molecule_ephemeral_directory_fixture(molecule_scenario_directory_fixture):
|
||||
path = pytest.helpers.molecule_ephemeral_directory(str(uuid4()))
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path)
|
||||
yield
|
||||
shutil.rmtree(str(Path(path).parent))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def molecule_file_fixture(
|
||||
molecule_scenario_directory_fixture, molecule_ephemeral_directory_fixture
|
||||
):
|
||||
return pytest.helpers.molecule_file()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_instance(molecule_file_fixture, molecule_data, request):
|
||||
mdc = copy.deepcopy(molecule_data)
|
||||
if hasattr(request, "param"):
|
||||
util.merge_dicts(mdc, request.getfixturevalue(request.param))
|
||||
pytest.helpers.write_molecule_file(molecule_file_fixture, mdc)
|
||||
c = config.Config(molecule_file_fixture)
|
||||
c.command_args = {"subcommand": "test"}
|
||||
|
||||
return c
|
||||
|
||||
|
||||
# Mocks
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_print_debug(mocker):
|
||||
return mocker.patch("molecule.util.print_debug")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_logger_info(mocker):
|
||||
return mocker.patch("logging.Logger.info")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_logger_out(mocker):
|
||||
return mocker.patch("molecule.logger.CustomLogger.out")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_logger_warn(mocker):
|
||||
return mocker.patch("logging.Logger.warn")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_logger_error(mocker):
|
||||
return mocker.patch("logging.Logger.error")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_logger_critical(mocker):
|
||||
return mocker.patch("logging.Logger.critical")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_logger_success(mocker):
|
||||
return mocker.patch("molecule.logger.CustomLogger.success")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_run_command(mocker):
|
||||
m = mocker.patch("molecule.util.run_command")
|
||||
m.return_value = mocker.Mock(stdout=b"patched-run-command-stdout")
|
||||
|
||||
return m
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_ansible_converge(mocker):
|
||||
m = mocker.patch("molecule.provisioner.ansible.Ansible.converge")
|
||||
m.return_value = "patched-ansible-converge-stdout"
|
||||
|
||||
return m
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_add_or_update_vars(mocker):
|
||||
return mocker.patch("molecule.provisioner.ansible.Ansible._add_or_update_vars")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_yamllint(mocker):
|
||||
return mocker.patch("molecule.lint.yamllint.Yamllint.execute")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_flake8(mocker):
|
||||
return mocker.patch("molecule.verifier.lint.flake8.Flake8.execute")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_ansible_galaxy(mocker):
|
||||
return mocker.patch("molecule.dependency.ansible_galaxy.AnsibleGalaxy.execute")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_testinfra(mocker):
|
||||
return mocker.patch("molecule.verifier.testinfra.Testinfra.execute")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_scenario_setup(mocker):
|
||||
mocker.patch("molecule.config.Config.env")
|
||||
|
||||
return mocker.patch("molecule.scenario.Scenario._setup")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_config_validate(mocker):
|
||||
return mocker.patch("molecule.config.Config._validate")
|
@ -0,0 +1,82 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import sh
|
||||
|
||||
from molecule import util
|
||||
from molecule.command.init import base
|
||||
from molecule.model import schema_v2
|
||||
|
||||
|
||||
class CommandBase(base.Base):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _base_class():
|
||||
return CommandBase
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _instance(_base_class):
|
||||
return _base_class()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _command_args():
|
||||
return {
|
||||
"dependency_name": "galaxy",
|
||||
"driver_name": "docker",
|
||||
"lint_name": "yamllint",
|
||||
"provisioner_name": "ansible",
|
||||
"scenario_name": "default",
|
||||
"role_name": "test-role",
|
||||
"verifier_name": "testinfra",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _role_directory():
|
||||
return "."
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_file(_role_directory):
|
||||
return os.path.join(
|
||||
_role_directory, "test-role", "molecule", "default", "molecule.yml"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("driver", [("hetznercloud")])
|
||||
def test_drivers(
|
||||
driver, temp_dir, _molecule_file, _role_directory, _command_args, _instance
|
||||
):
|
||||
_command_args["driver_name"] = driver
|
||||
_instance._process_templates("molecule", _command_args, _role_directory)
|
||||
|
||||
data = util.safe_load_file(_molecule_file)
|
||||
|
||||
assert {} == schema_v2.validate(data)
|
||||
|
||||
cmd = sh.yamllint.bake("-s", _molecule_file)
|
||||
pytest.helpers.run_command(cmd)
|
0
molecule_hetznercloud/test/unit/driver/__init__.py
Normal file
0
molecule_hetznercloud/test/unit/driver/__init__.py
Normal file
200
molecule_hetznercloud/test/unit/driver/test_hetznercloud.py
Normal file
200
molecule_hetznercloud/test/unit/driver/test_hetznercloud.py
Normal file
@ -0,0 +1,200 @@
|
||||
# Copyright (c) 2019 Red Hat, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from molecule import config
|
||||
from molecule.driver import hetznercloud
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hetznercloud_instance(patched_config_validate, config_instance):
|
||||
return hetznercloud.HetznerCloud(config_instance)
|
||||
|
||||
|
||||
def test_hetznercloud_config_gives_config_object(hetznercloud_instance):
|
||||
assert isinstance(hetznercloud_instance._config, config.Config)
|
||||
|
||||
|
||||
def test_hetznercloud_testinfra_options_property(hetznercloud_instance):
|
||||
assert {
|
||||
"connection": "ansible",
|
||||
"ansible-inventory": hetznercloud_instance._config.provisioner.inventory_file,
|
||||
} == hetznercloud_instance.testinfra_options
|
||||
|
||||
|
||||
def test_hetznercloud_name_property(hetznercloud_instance):
|
||||
assert "hetznercloud" == hetznercloud_instance.name
|
||||
|
||||
|
||||
def test_hetznercloud_options_property(hetznercloud_instance):
|
||||
assert {"managed": True} == hetznercloud_instance.options
|
||||
|
||||
|
||||
def test_hetznercloud_login_cmd_template_property(hetznercloud_instance):
|
||||
template = "ssh {address} -l {user} -p {port}"
|
||||
assert template in hetznercloud_instance.login_cmd_template
|
||||
|
||||
|
||||
def test_hetznercloud_safe_files_property(hetznercloud_instance):
|
||||
expected_safe_files = [
|
||||
os.path.join(
|
||||
hetznercloud_instance._config.scenario.ephemeral_directory,
|
||||
"instance_config.yml",
|
||||
)
|
||||
]
|
||||
|
||||
assert expected_safe_files == hetznercloud_instance.safe_files
|
||||
|
||||
|
||||
def test_hetznercloud_default_safe_files_property(hetznercloud_instance):
|
||||
expected_default_safe_files = [
|
||||
os.path.join(
|
||||
hetznercloud_instance._config.scenario.ephemeral_directory,
|
||||
"instance_config.yml",
|
||||
)
|
||||
]
|
||||
assert expected_default_safe_files == hetznercloud_instance.default_safe_files
|
||||
|
||||
|
||||
def test_hetznercloud_delegated_property(hetznercloud_instance):
|
||||
assert not hetznercloud_instance.delegated
|
||||
|
||||
|
||||
def test_hetznercloud_managed_property(hetznercloud_instance):
|
||||
assert hetznercloud_instance.managed
|
||||
|
||||
|
||||
def test_hetznercloud_default_ssh_connection_options_property(hetznercloud_instance):
|
||||
expected_options = [
|
||||
"-o UserKnownHostsFile=/dev/null",
|
||||
"-o ControlMaster=auto",
|
||||
"-o ControlPersist=60s",
|
||||
"-o IdentitiesOnly=yes",
|
||||
"-o StrictHostKeyChecking=no",
|
||||
]
|
||||
|
||||
assert expected_options == (hetznercloud_instance.default_ssh_connection_options)
|
||||
|
||||
|
||||
def test_hetznercloud_login_options(hetznercloud_instance, mocker):
|
||||
target = "molecule.driver.hetznercloud.HetznerCloud._get_instance_config"
|
||||
get_instance_config_patch = mocker.patch(target)
|
||||
|
||||
get_instance_config_patch.return_value = {
|
||||
"instance": "hetznercloud",
|
||||
"address": "172.16.0.2",
|
||||
"user": "hetzner-admin",
|
||||
"port": 22,
|
||||
}
|
||||
|
||||
get_instance_config_patch = {
|
||||
"instance": "hetznercloud",
|
||||
"address": "172.16.0.2",
|
||||
"user": "hetzner-admin",
|
||||
"port": 22,
|
||||
}
|
||||
|
||||
assert get_instance_config_patch == hetznercloud_instance.login_options(
|
||||
"hetznercloud"
|
||||
)
|
||||
|
||||
|
||||
def test_hetznercloud_ansible_connection_opts(hetznercloud_instance, mocker):
|
||||
target = "molecule.driver.hetznercloud.HetznerCloud._get_instance_config"
|
||||
get_instance_config_patch = mocker.patch(target)
|
||||
|
||||
get_instance_config_patch.return_value = {
|
||||
"instance": "hetznercloud",
|
||||
"address": "172.16.0.2",
|
||||
"user": "hetzner-admin",
|
||||
"port": 22,
|
||||
"identity_file": "/foo/bar",
|
||||
}
|
||||
|
||||
get_instance_config_patch = {
|
||||
"ansible_host": "172.16.0.2",
|
||||
"ansible_port": 22,
|
||||
"ansible_user": "hetzner-admin",
|
||||
"ansible_private_key_file": "/foo/bar",
|
||||
"connection": "ssh",
|
||||
"ansible_ssh_common_args": (
|
||||
"-o UserKnownHostsFile=/dev/null "
|
||||
"-o ControlMaster=auto "
|
||||
"-o ControlPersist=60s "
|
||||
"-o IdentitiesOnly=yes "
|
||||
"-o StrictHostKeyChecking=no"
|
||||
),
|
||||
}
|
||||
|
||||
connection_options = hetznercloud_instance.ansible_connection_options(
|
||||
"hetznercloud"
|
||||
)
|
||||
assert get_instance_config_patch == connection_options
|
||||
|
||||
|
||||
def test_hetznercloud_instance_config_property(hetznercloud_instance):
|
||||
instance_config_path = os.path.join(
|
||||
hetznercloud_instance._config.scenario.ephemeral_directory,
|
||||
"instance_config.yml",
|
||||
)
|
||||
|
||||
assert instance_config_path == hetznercloud_instance.instance_config
|
||||
|
||||
|
||||
def test_hetznercloud_ssh_connection_options_property(hetznercloud_instance):
|
||||
expected_options = [
|
||||
"-o UserKnownHostsFile=/dev/null",
|
||||
"-o ControlMaster=auto",
|
||||
"-o ControlPersist=60s",
|
||||
"-o IdentitiesOnly=yes",
|
||||
"-o StrictHostKeyChecking=no",
|
||||
]
|
||||
|
||||
assert expected_options == hetznercloud_instance.ssh_connection_options
|
||||
|
||||
|
||||
def test_hetznercloud_status(mocker, hetznercloud_instance):
|
||||
hetzner_status = hetznercloud_instance.status()
|
||||
|
||||
assert 2 == len(hetzner_status)
|
||||
|
||||
assert hetzner_status[0].instance_name == "instance-1"
|
||||
assert hetzner_status[0].driver_name == "hetznercloud"
|
||||
assert hetzner_status[0].provisioner_name == "ansible"
|
||||
assert hetzner_status[0].scenario_name == "default"
|
||||
assert hetzner_status[0].created == "false"
|
||||
assert hetzner_status[0].converged == "false"
|
||||
|
||||
assert hetzner_status[1].instance_name == "instance-2"
|
||||
assert hetzner_status[1].driver_name == "hetznercloud"
|
||||
assert hetzner_status[1].provisioner_name == "ansible"
|
||||
assert hetzner_status[1].scenario_name == "default"
|
||||
assert hetzner_status[1].created == "false"
|
||||
assert hetzner_status[1].converged == "false"
|
||||
|
||||
|
||||
def test_created(hetznercloud_instance):
|
||||
assert "false" == hetznercloud_instance._created()
|
||||
|
||||
|
||||
def test_converged(hetznercloud_instance):
|
||||
assert "false" == hetznercloud_instance._converged()
|
0
molecule_hetznercloud/test/unit/model/__init__.py
Normal file
0
molecule_hetznercloud/test/unit/model/__init__.py
Normal file
46
molecule_hetznercloud/test/unit/model/v2/conftest.py
Normal file
46
molecule_hetznercloud/test/unit/model/v2/conftest.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from molecule import util
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _molecule_file():
|
||||
return os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
os.path.pardir,
|
||||
os.path.pardir,
|
||||
os.path.pardir,
|
||||
"resources",
|
||||
"molecule_docker.yml",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _config(_molecule_file, request):
|
||||
d = util.safe_load(open(_molecule_file))
|
||||
if hasattr(request, "param"):
|
||||
d = util.merge_dicts(d, request.getfixturevalue(request.param))
|
||||
|
||||
return d
|
@ -0,0 +1,101 @@
|
||||
# Copyright (c) 2015-2018 Cisco Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import pytest
|
||||
|
||||
from molecule.model import schema_v2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _model_platform_hetznercloud_section_data():
|
||||
return {
|
||||
"driver": {"name": "hetznercloud"},
|
||||
"platforms": [
|
||||
{
|
||||
"name": "instance",
|
||||
"server_type": "",
|
||||
"volumes": [""],
|
||||
"image": "",
|
||||
"location": "",
|
||||
"datacenter": "",
|
||||
"user_data": "",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"_config", ["_model_platform_hetznercloud_section_data"], indirect=True
|
||||
)
|
||||
def test_platforms_hetznercloud(_config):
|
||||
assert {} == schema_v2.validate(_config)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _model_platforms_hetznercloud_errors_section_data():
|
||||
return {
|
||||
"driver": {"name": "hetznercloud"},
|
||||
"platforms": [
|
||||
{
|
||||
"name": 0,
|
||||
"server_type": 0,
|
||||
"volumes": {},
|
||||
"image": 0,
|
||||
"location": 0,
|
||||
"datacenter": 0,
|
||||
"user_data": 0,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"_config", ["_model_platforms_hetznercloud_errors_section_data"], indirect=True
|
||||
)
|
||||
def test_platforms_hetznercloud_has_errors(_config):
|
||||
expected_config = {
|
||||
"platforms": [
|
||||
{
|
||||
0: [
|
||||
{
|
||||
"name": ["must be of string type"],
|
||||
"server_type": ["must be of string type"],
|
||||
"volumes": ["must be of list type"],
|
||||
"image": ["must be of string type"],
|
||||
"location": ["must be of string type"],
|
||||
"datacenter": ["must be of string type"],
|
||||
"user_data": ["must be of string type"],
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
assert expected_config == schema_v2.validate(_config)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"_config", ["_model_platform_hetznercloud_section_data"], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("_required_field", ("server_type", "image"))
|
||||
def test_platforms_hetznercloud_fields_required(_config, _required_field):
|
||||
del _config["platforms"][0][_required_field]
|
||||
expected_config = {"platforms": [{0: [{_required_field: ["required field"]}]}]}
|
||||
assert expected_config == schema_v2.validate(_config)
|
Reference in New Issue
Block a user