diff --git a/changelogs/fragments/68288-galaxy-requirements-install-only.yml b/changelogs/fragments/68288-galaxy-requirements-install-only.yml new file mode 100644 index 00000000000..d3284fb708e --- /dev/null +++ b/changelogs/fragments/68288-galaxy-requirements-install-only.yml @@ -0,0 +1,2 @@ +minor_changes: + - ansible-galaxy - allow role to define dependency requirements that will be only installed by defining them in ``meta/requirements.yml`` (https://github.com/ansible/proposals/issues/57) diff --git a/docs/docsite/rst/galaxy/user_guide.rst b/docs/docsite/rst/galaxy/user_guide.rst index 8fcdc2e2bfc..8a2f7dbb8d0 100644 --- a/docs/docsite/rst/galaxy/user_guide.rst +++ b/docs/docsite/rst/galaxy/user_guide.rst @@ -363,10 +363,30 @@ command line, as follows: Dependencies ------------ -Roles can also be dependent on other roles, and when you install a role that has dependencies, those dependencies will automatically be installed. +Roles can also be dependent on other roles, and when you install a role that has dependencies, those dependencies will automatically be installed to the ``roles_path``. -You specify role dependencies in the ``meta/main.yml`` file by providing a list of roles. If the source of a role is Galaxy, you can simply specify the role in -the format ``namespace.role_name``. You can also use the more complex format in ``requirements.yml``, allowing you to provide ``src``, ``scm``, ``version``, and ``name``. +There are two ways to define the dependencies of a role: + +* using ``meta/requirements.yml`` +* using ``meta/main.yml`` + +Using ``meta/requirements.yml`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`.. versionadded:: 2.10` + +You can create the file ``meta/requirements.yml`` and define dependencies in the same format used for :file:`requirements.yml` described in the `Installing multiple roles from a file`_ section. + +From there, you can import or include the specified roles in your tasks. + +Using ``meta/main.yml`` +^^^^^^^^^^^^^^^^^^^^^^^ + +Alternatively, you can specify role dependencies in the ``meta/main.yml`` file by providing a list of roles under the ``dependencies`` section. If the source of a role is Galaxy, you can simply specify the role in +the format ``namespace.role_name``. You can also use the more complex format in :file:`requirements.yml`, allowing you to provide ``src``, ``scm``, ``version``, and ``name``. + +Dependencies installed that way, depending on other factors described below, will also be executed **before** this role is executed during play execution. +To better understand how dependencies are handled during play execution, see :ref:`playbooks_reuse_roles`. The following shows an example ``meta/main.yml`` file with dependent roles: @@ -425,8 +445,6 @@ Alternately, you can specify the role dependencies in the complex form used in src: git+https://github.com/geerlingguy/ansible-role-composer.git version: 775396299f2da1f519f0d8885022ca2d6ee80ee8 -When dependencies are encountered by ``ansible-galaxy``, it will automatically install each dependency to the ``roles_path``. To understand how dependencies are handled during play execution, see :ref:`playbooks_reuse_roles`. - .. note:: Galaxy expects all role dependencies to exist in Galaxy, and therefore dependencies to be specified in the diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 0aef3665f53..4f6b4a14d2b 100644 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -1057,7 +1057,7 @@ class GalaxyCLI(CLI): if not role.metadata: display.warning("Meta file %s is empty. Skipping dependencies." % role.path) else: - role_dependencies = role.metadata.get('dependencies') or [] + role_dependencies = (role.metadata.get('dependencies') or []) + role.requirements for dep in role_dependencies: display.debug('Installing dep %s' % dep) dep_req = RoleRequirement() diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py index ba12d1d4c28..75144d852b8 100644 --- a/lib/ansible/galaxy/role.py +++ b/lib/ansible/galaxy/role.py @@ -47,11 +47,13 @@ class GalaxyRole(object): SUPPORTED_SCMS = set(['git', 'hg']) META_MAIN = (os.path.join('meta', 'main.yml'), os.path.join('meta', 'main.yaml')) META_INSTALL = os.path.join('meta', '.galaxy_install_info') + META_REQUIREMENTS = (os.path.join('meta', 'requirements.yml'), os.path.join('meta', 'requirements.yaml')) ROLE_DIRS = ('defaults', 'files', 'handlers', 'meta', 'tasks', 'templates', 'vars', 'tests') def __init__(self, galaxy, api, name, src=None, version=None, scm=None, path=None): self._metadata = None + self._requirements = None self._install_info = None self._validate_certs = not context.CLIARGS['ignore_certs'] @@ -371,3 +373,25 @@ class GalaxyRole(object): } """ return dict(scm=self.scm, src=self.src, version=self.version, name=self.name) + + @property + def requirements(self): + """ + Returns role requirements + """ + if self._requirements is None: + self._requirements = [] + for meta_requirements in self.META_REQUIREMENTS: + meta_path = os.path.join(self.path, meta_requirements) + if os.path.isfile(meta_path): + try: + f = open(meta_path, 'r') + self._requirements = yaml.safe_load(f) + except Exception: + display.vvvvv("Unable to load requirements for %s" % self.name) + finally: + f.close() + + break + + return self._requirements diff --git a/test/integration/targets/ansible-galaxy/runme.sh b/test/integration/targets/ansible-galaxy/runme.sh index e33dc02de60..0af7f20f7ba 100755 --- a/test/integration/targets/ansible-galaxy/runme.sh +++ b/test/integration/targets/ansible-galaxy/runme.sh @@ -12,35 +12,71 @@ ansible-galaxy --version # Need a relative custom roles path for testing various scenarios of -p galaxy_relative_rolespath="my/custom/roles/path" -# Prep the local git repo with a role and make a tar archive so we can test +# Status message function (f_ to designate that it's a function) +f_ansible_galaxy_status() +{ + printf "\n\n\n### Testing ansible-galaxy: %s\n" "${@}" +} + +# Use to initialize a repository. Must call the post function too. +f_ansible_galaxy_create_role_repo_pre() +{ + repo_name=$1 + repo_dir=$2 + + pushd "${repo_dir}" + ansible-galaxy init "${repo_name}" + pushd "${repo_name}" + git init . + + # Prep git, because it doesn't work inside a docker container without it + git config user.email "tester@ansible.com" + git config user.name "Ansible Tester" + + # f_ansible_galaxy_create_role_repo_post +} + +# Call after f_ansible_galaxy_create_repo_pre. +f_ansible_galaxy_create_role_repo_post() +{ + repo_name=$1 + repo_tar=$2 + + # f_ansible_galaxy_create_role_repo_pre + + git add . + git commit -m "local testing ansible galaxy role" + + git archive \ + --format=tar \ + --prefix="${repo_name}/" \ + master > "${repo_tar}" + popd # "${repo_name}" + popd # "${repo_dir}" +} + +# Prep the local git repos with role and make a tar archive so we can test # different things galaxy_local_test_role="test-role" galaxy_local_test_role_dir=$(mktemp -d) galaxy_local_test_role_git_repo="${galaxy_local_test_role_dir}/${galaxy_local_test_role}" galaxy_local_test_role_tar="${galaxy_local_test_role_dir}/${galaxy_local_test_role}.tar" -pushd "${galaxy_local_test_role_dir}" - ansible-galaxy init "${galaxy_local_test_role}" - pushd "${galaxy_local_test_role}" - git init . - - # Prep git, becuase it doesn't work inside a docker container without it - git config user.email "tester@ansible.com" - git config user.name "Ansible Tester" - - git add . - git commit -m "local testing ansible galaxy role" - git archive \ - --format=tar \ - --prefix="${galaxy_local_test_role}/" \ - master > "${galaxy_local_test_role_tar}" - popd # "${galaxy_local_test_role}" -popd # "${galaxy_local_test_role_dir}" -# Status message function (f_ to designate that it's a function) -f_ansible_galaxy_status() -{ - printf "\n\n\n### Testing ansible-galaxy: %s\n" "${@}" -} +f_ansible_galaxy_create_role_repo_pre "${galaxy_local_test_role}" "${galaxy_local_test_role_dir}" +f_ansible_galaxy_create_role_repo_post "${galaxy_local_test_role}" "${galaxy_local_test_role_tar}" + +galaxy_local_parent_role="parent-role" +galaxy_local_parent_role_dir=$(mktemp -d) +galaxy_local_parent_role_git_repo="${galaxy_local_parent_role_dir}/${galaxy_local_parent_role}" +galaxy_local_parent_role_tar="${galaxy_local_parent_role_dir}/${galaxy_local_parent_role}.tar" + +# Create parent-role repository +f_ansible_galaxy_create_role_repo_pre "${galaxy_local_parent_role}" "${galaxy_local_parent_role_dir}" + + cat < meta/requirements.yml +- src: git+file:///${galaxy_local_test_role_git_repo} +EOF +f_ansible_galaxy_create_role_repo_post "${galaxy_local_parent_role}" "${galaxy_local_parent_role_tar}" # Galaxy install test case # @@ -55,6 +91,7 @@ pushd "${galaxy_testdir}" [[ -d "${HOME}/.ansible/roles/${galaxy_local_test_role}" ]] popd # ${galaxy_testdir} rm -fr "${galaxy_testdir}" +rm -fr "${HOME}/.ansible/roles/${galaxy_local_test_role}" # Galaxy install test case # @@ -71,6 +108,45 @@ pushd "${galaxy_testdir}" popd # ${galaxy_testdir} rm -fr "${galaxy_testdir}" +# Galaxy install test case +# +# Install local git repo with a meta/requirements.yml +f_ansible_galaxy_status "install of local git repo with meta/requirements.yml" +galaxy_testdir=$(mktemp -d) +pushd "${galaxy_testdir}" + + ansible-galaxy install git+file:///"${galaxy_local_parent_role_git_repo}" "$@" + + # Test that the role was installed to the expected directory + [[ -d "${HOME}/.ansible/roles/${galaxy_local_parent_role}" ]] + + # Test that the dependency was also installed + [[ -d "${HOME}/.ansible/roles/${galaxy_local_test_role}" ]] + +popd # ${galaxy_testdir} +rm -fr "${galaxy_testdir}" +rm -fr "${HOME}/.ansible/roles/${galaxy_local_parent_role}" +rm -fr "${HOME}/.ansible/roles/${galaxy_local_test_role}" + +# Galaxy install test case +# +# Install local git repo with a meta/requirements.yml + --no-deps argument +f_ansible_galaxy_status "install of local git repo with meta/requirements.yml + --no-deps argument" +galaxy_testdir=$(mktemp -d) +pushd "${galaxy_testdir}" + + ansible-galaxy install git+file:///"${galaxy_local_parent_role_git_repo}" --no-deps "$@" + + # Test that the role was installed to the expected directory + [[ -d "${HOME}/.ansible/roles/${galaxy_local_parent_role}" ]] + + # Test that the dependency was not installed + [[ ! -d "${HOME}/.ansible/roles/${galaxy_local_test_role}" ]] + +popd # ${galaxy_testdir} +rm -fr "${galaxy_testdir}" +rm -fr "${HOME}/.ansible/roles/${galaxy_local_test_role}" + # Galaxy install test case # # Ensure that if both a role_file and role_path is provided, they are both @@ -112,13 +188,16 @@ rm -fr "${galaxy_testdir}" # # Basic tests to ensure listing roles works -f_ansible_galaxy_status \ - "role list" +f_ansible_galaxy_status "role list" +galaxy_testdir=$(mktemp -d) +pushd "${galaxy_testdir}" + ansible-galaxy install git+file:///"${galaxy_local_test_role_git_repo}" "$@" ansible-galaxy role list | tee out.txt ansible-galaxy role list test-role | tee -a out.txt [[ $(grep -c '^- test-role' out.txt ) -eq 2 ]] +popd # ${galaxy_testdir} # Galaxy role test case # @@ -185,7 +264,6 @@ f_ansible_galaxy_status \ popd # ${role_testdir} rm -fr "${role_testdir}" - # Properly list roles when the role name is a subset of the path, or the role # name is the same name as the parent directory of the role. Issue #67365 #