5d05327449
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
154 lines
6.4 KiB
Python
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')
|