ansible/test/units/playbook/role/test_definition.py
Adrian Likins 5d05327449 Support loading mazer/galaxy roles from content path
Allow roles to be referenced, found, and loaded by using
a galaxy/mazer style role name. ie, "geerlingguy.nginx.nginx'
or 'namespace.reponame.contentname'

When used with galaxy roles installed via 'mazer'
(https://github.com/ansible/mazer) this lets roles
to be referenced by the mazer/galaxy style name.

See https://galaxy.ansible.com/docs/mazer/examples.html#using-mazer-content
for more info on how/where mazer installs galaxy content.

mazer installed content lives in ~/.ansible/content/

Inside of ~/.ansible/content, there are directories for
each galaxy namespace (more or less the github org or
user id used in galaxy roles). For ex

        ~/.ansible/content/alikins

Inside each namespace directory, there will be a directory
for each galaxy 'repo' installed. For a traditional galaxy
role, this repo dir will have a name that matches the role
name.
See https://galaxy.ansible.com/docs/mazer/examples.html#installing-roles

For example, geerlingguy.apache will get installed to:

        ~/.ansible/content/geerlingguy/apache

For new multi-content style repos
(see
https://galaxy.ansible.com/docs/mazer/examples.html#installing-multi-role-repositories)
the repo level directory name with match the name of the git repo
imported to galaxy. For example, for the github repo
at https://github.com/atestuseraccount/ansible-testing-content imported
to galaxy-qa at https://galaxy-qa.ansible.com/testing/ansible_testing_content, the
repo level directory name is 'ansible_testing_content'.

Inside the repo level dir, there are directories for each content
type supported by galaxy. For example, 'roles'.

Inside each content type dir, there will be a dir named for the
each instance of that content. For the 'testing' example above,
the 'test-role-a' role will be installed to:

        ~/.ansible/content/testing/ansible_testing_content/roles/test-role-a

To use test-role-a in a playbook, it can be referenced as
'testing.ansible_testing_content.test-role-a'

For a traditional role (one where the git repo contains only
a single role) like geerlingguy.apache, mazer will install it
to:

        ~/.ansible/content/geerlingguy/apache/roles/apache

To reference that role in a playbook, there is a full qualified
name and a shorter alias.

The fully qualified name in this case
would be 'geerlingguy.apache.apache'. That is
"namespace.reponame.contentname".

The shorter alias is 'geerlingguy.apache'. Note that this name
format is compatible with using roles installed with ansible-galaxy.

ie, 'mynamespace.myrole' will try to look for a role named
'repo' at ~/.ansible/content/mynamespace/myrole/roles/myrole

ie, 'geerlingguy.apache' ->
~/.ansible/content/geerlingguy/apache/roles/apache

in addition to more specific
'geerlingguy.apache.apache' ->
~/.ansible/content/geerlingguy/apache/roles/apache

or
'testing.some_multi_content_repo.some_role' ->
~/.ansible/content/testing/some_multi_content_repo/roles/some_role

But NOT:
'testing.some_multi_content_repo' -> role loading error (assuming
there isnt also a role named 'some_multi_content_repo')

and NOT:
'testing.myrole' -> role loading error (no repo info)

- Extract content path role loading to method
- Start removing use of installed_role_spec, seems
we dont need it.
- Eemove use of passed in installed_role_spec ds. We just need the role_name.
- Rename local 'ds' to role_name_parts since ds has a meaning elsewhere
- Split out the role->content relative path parsing
- Add some unit test coverage for playbook.roles.definition
2018-07-25 15:57:13 -04:00

154 lines
6.4 KiB
Python

import logging
import os.path
import pytest
from ansible import errors
from ansible.playbook.role import definition
log = logging.getLogger(__name__)
def test_role_definition(mocker):
res = definition.RoleDefinition(play=None, role_basedir=None, variable_manager=None, loader=None)
assert isinstance(res, definition.RoleDefinition)
def test_role_definition_load_role_path_from_content_path(mocker):
mock_loader = mocker.Mock(name='MockDataLoader')
mock_loader.get_basedir = mocker.Mock(return_value='/dev/null/roles')
mock_loader.path_exists = mocker.Mock(return_value=True)
content_path = '/dev/null/content/'
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_CONTENT_PATH',
[content_path])
rd = definition.RoleDefinition(play=None, role_basedir=None, variable_manager=None, loader=mock_loader)
role_name = 'namespace.repo.role'
res = rd._load_role_path(role_name)
log.debug('res: %s', res)
assert isinstance(res, tuple)
assert res[0] == role_name
assert res[1] == os.path.join(content_path, 'namespace/repo/roles/role')
def test_role_definition_load_role_path_from_role_path(mocker):
mock_loader = mocker.Mock(name='MockDataLoader')
mock_loader.get_basedir = mocker.Mock(return_value='/dev/null/playbook_rel_roles_dir')
mock_loader.path_exists = mocker.Mock(return_value=True)
content_path = '/dev/null/content/'
role_path = '/dev/null/the_default_roles_path'
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_CONTENT_PATH',
[content_path])
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_ROLES_PATH',
[role_path])
rd = definition.RoleDefinition(play=None, role_basedir='/dev/null/role_basedir', variable_manager=None, loader=mock_loader)
role_name = 'some_role'
rd.preprocess_data({'role': role_name})
res = rd._load_role_path(role_name)
log.debug('res: %s', res)
assert isinstance(res, tuple)
# The playbook rel roles/ dir is the first path checked
assert res[1] == '/dev/null/playbook_rel_roles_dir/roles/some_role'
def test_role_definition_load_role_path_from_role_path_not_found(mocker):
mock_loader = mocker.Mock(name='MockDataLoader')
mock_loader.get_basedir = mocker.Mock(return_value='/dev/null/playbook_rel_roles_dir')
mock_loader.path_exists = mocker.Mock(return_value=False)
content_path = '/dev/null/content/'
role_path = '/dev/null/the_default_roles_path'
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_CONTENT_PATH',
[content_path])
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_ROLES_PATH',
[role_path])
rd = definition.RoleDefinition(play=None, role_basedir='/dev/null/role_basedir', variable_manager=None, loader=mock_loader)
role_name = 'some_role'
with pytest.raises(errors.AnsibleError, match='.'):
# pres = rd.preprocess_data({'role': role_name})
pres = rd.preprocess_data(role_name)
log.debug('pres: %s', pres)
rd._load_role_path(role_name)
def test_role_definition_load_role_no_name_in_role_ds(mocker):
mock_loader = mocker.Mock(name='MockDataLoader')
mock_loader.get_basedir = mocker.Mock(return_value='/dev/null/playbook_rel_roles_dir')
mock_loader.path_exists = mocker.Mock(return_value=True)
content_path = '/dev/null/content/'
role_path = '/dev/null/the_default_roles_path'
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_CONTENT_PATH',
[content_path])
mocker.patch('ansible.playbook.role.definition.C.DEFAULT_ROLES_PATH',
[role_path])
rd = definition.RoleDefinition(play=None, role_basedir='/dev/null/role_basedir', variable_manager=None, loader=mock_loader)
role_name = 'some_role'
with pytest.raises(errors.AnsibleError, match='role definitions must contain a role name'):
rd.preprocess_data({'stuff': role_name,
'things_i_like': ['cheese', 'naps']})
@pytest.mark.parametrize("role_name,expected",
[('namespace.repo.role', 'namespace/repo/roles/role'),
('a.a.a', 'a/a/roles/a'),
('too.many.dots.lets.assume.extra.dots.are.role.name',
'too/many/roles/dots.lets.assume.extra.dots.are.role.name'),
# valid if role names can include sub paths? should raise an error?
('ns.repo.role/name/roles/role', 'ns/repo/roles/role/name/roles/role'),
# DWIM
('geerlingguy.apache', 'geerlingguy/apache/roles/apache'),
# these are actually legit 'role_name' inside ansible
# they get used as relative paths
('../../some_dir/foo', None),
('../some_dir.foo.bar', None),
('.', None),
('/', None),
('./foo', None),
('.', None),
('justaname', None),
('namespace_dot.', None),
('somenamespace/somerepo/roles/somerole', None),
(None, None),
])
def test_role_name_to_relative_content_path(role_name, expected):
res = definition.role_name_to_relative_content_path(role_name)
log.debug('res: %s', res)
assert res == expected
def test_find_role_in_content_path_invalid_role_name(mocker):
mock_loader = mocker.Mock(return_value=False)
res = definition.find_role_in_content_path('justaname', mock_loader, '/dev/null/foo')
assert res is None
def test_find_role_in_content_path_loader_cant_find(mocker):
mock_loader = mocker.Mock(path_exists=mocker.Mock(return_value=False))
res = definition.find_role_in_content_path('namespace.repo.rolename', mock_loader, ['/dev/null/foo'])
assert res is None
def test_find_role_in_content_path_loader(mocker):
content_search_path = '/dev/null/foo'
role_name = 'namespace.repo.rolename'
mock_loader = mocker.Mock(path_exists=mocker.Mock(return_value=True))
res = definition.find_role_in_content_path(role_name, mock_loader, [content_search_path])
assert isinstance(res, tuple)
assert res[0] == role_name
assert res[1] == os.path.join(content_search_path, 'namespace/repo/roles/rolename')