Skip to content

Commit

Permalink
provision.localhost: Add prepare functionality
Browse files Browse the repository at this point in the history
Adding 'ansible' and 'shell' steps to local provisioner.

Adding a few tests for the provisioner along the way :)

Signed-off-by: Miroslav Vadkerti <mvadkert@redhat.com>
  • Loading branch information
thrix committed Nov 13, 2019
1 parent d8a0749 commit 40a0785
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 15 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -4,6 +4,7 @@ python:
- "3.7"
before_install:
- "pip install -U pip setuptools virtualenv python-coveralls fmf click"
- "sudo apt-get -y install vagrant"
script:
- "coverage run --source=bin,tmt -m py.test $CAPTURE tests"
after_success:
Expand Down
93 changes: 93 additions & 0 deletions tests/unit/steps/test_provision.py
@@ -0,0 +1,93 @@
import mock
import os
import pytest
import unittest
import tempfile

from tmt.steps.provision import Provision, localhost, vagrant
from tmt.utils import GeneralError, SpecificationError


class PlanMock(mock.MagicMock):
workdir = tempfile.mkdtemp()

def opt(self, *args, **kwargs):
return {}

def _level(self):
return 0


def test_empty_plan():
provision = Provision({}, None)
with pytest.raises(GeneralError):
provision.wake()


def test_defaults():
plan = mock.MagicMock()
provision = Provision({}, PlanMock())
provision.wake()
for provision_data in provision.data:
assert provision_data['how'] == 'virtual'


def test_unsupported_provisioner():
provision = Provision({'how': 'not-a-valid-provisioner'}, PlanMock())
with pytest.raises(SpecificationError):
provision.wake()


@pytest.mark.parametrize('how,provisioner', [
('libvirt', vagrant.ProvisionVagrant),
('virtual', vagrant.ProvisionVagrant),
('vagrant', vagrant.ProvisionVagrant),
('local', localhost.ProvisionLocalhost),
('localhost', localhost.ProvisionLocalhost)
])
def test_pick_provision(how, provisioner):
plan = PlanMock()
provision = Provision({'how': how}, plan)
provision.wake()
for guest in provision.guests:
assert isinstance(guest, provisioner)


def test_localhost_execute():
plan = PlanMock()
provision = Provision({'how': 'localhost'}, plan)
provision.wake()

with mock.patch('tmt.utils.Common.run') as run:
provision.execute('a', 'b', 'c')
run.assert_called_once_with('a b c')


def test_localhost_execute():
plan = PlanMock()
provision = Provision({'how': 'localhost'}, plan)
provision.wake()

with mock.patch('tmt.utils.Common.run') as run:
provision.execute('a', 'b', 'c')
run.assert_called_once_with('a b c')


def test_localhost_prepare_ansible():
plan = PlanMock()
provision = Provision({'how': 'localhost'}, plan)
provision.wake()

with mock.patch('tmt.utils.Common.run') as run:
provision.prepare('ansible', 'playbook.yml')
run.assert_called_once_with('ansible-playbook -c local -i localhost, playbook.yml', cwd=os.getcwd())


def test_localhost_prepare_shell():
plan = PlanMock()
provision = Provision({'how': 'localhost'}, plan)
provision.wake()

with mock.patch('tmt.utils.Common.run') as run:
provision.prepare('shell', 'a b c')
run.assert_called_once_with('a b c', cwd=os.getcwd())
2 changes: 1 addition & 1 deletion tmt/steps/__init__.py
Expand Up @@ -69,7 +69,7 @@ def wake(self):
# Override data with command line input
for step in self.data:
how = self.opt('how')
if how is not None:
if how:
step['how'] = how

def go(self):
Expand Down
47 changes: 34 additions & 13 deletions tmt/steps/provision/__init__.py
Expand Up @@ -8,36 +8,57 @@
from click import echo

from tmt.utils import SpecificationError
from tmt.steps.provision import vagrant, localhost


class Provision(tmt.steps.Step):
""" Provision step """

# Default implementation for provision is a virtual machine
name = 'provision'

# supported provisioners are not loaded automatically, import them and map them in how_map
how_map = {
'vagrant': vagrant.ProvisionVagrant,
'libvirt': vagrant.ProvisionVagrant,
'virtual': vagrant.ProvisionVagrant,
'local': localhost.ProvisionLocalhost,
'localhost': localhost.ProvisionLocalhost
}

# default provisioner
how = 'virtual'

def __init__(self, data, plan):
super(Provision, self).__init__(data, plan)
# List of provisioned guests
self.guests = []

# Parent
self.super = super(Provision, self)

# Initialize parent
self.super.__init__(data, plan)

def _check_data(self):
""" Validate input data """

# if not specified, use 'virtual' provisioner as default
for data in self.data:
how = data['how']
# is how supported?
if how not in self.how_map:
raise tmt.utils.SpecificationError("How '{}' in plan '{}' is not implemented".format(how, self.plan))

def wake(self):
""" Wake up the step (process workdir and command line) """
super(Provision, self).wake()

self._check_data()
image = self.opt('image')
# Choose the plugin

# Add plugins for all guests
for data in self.data:
how = data.get('how')
# Update the image if provided
if image is not None:
if image:
data['image'] = image
if how == 'local':
from .localhost import ProvisionLocalhost
self.guests.append(ProvisionLocalhost(data, self))
else:
from .vagrant import ProvisionVagrant
self.guests.append(ProvisionVagrant(data, self))
self.guests.append(self.how_map[data['how']](data, self))

def go(self):
""" Provision all resources """
Expand Down
31 changes: 30 additions & 1 deletion tmt/steps/provision/localhost.py
@@ -1,7 +1,36 @@
from click import echo
import os

from click import echo
from tmt.steps.provision.base import ProvisionBase
from tmt.utils import SpecificationError


class ProvisionLocalhost(ProvisionBase):
""" Localhost provisioner """

def __init__(self, data, step):
super(ProvisionLocalhost, self).__init__(data, step)
self._prepare_map = {
'ansible': self._prepare_ansible,
'shell': self._prepare_shell,
}

def execute(self, *args, **kwargs):
self.run(self.join(args))

def _prepare_ansible(self, what):
""" Run ansible on localhost """
# note: we expect playbooks are placed relatively to the current directory
self.run(f'ansible-playbook -c local -i localhost, {what}', cwd=os.getcwd())

def _prepare_shell(self, what):
""" Run ansible on localhost """
# note: we expect playbooks are placed relatively to the current directory
self.run(what, cwd=os.getcwd())

def prepare(self, how, what):
""" Run prepare phase """
try:
self._prepare_map[how](what)
except AttributeError as e:
raise SpecificationError(f"How '{how}' is not supported.")

0 comments on commit 40a0785

Please sign in to comment.