Ansible Transitive Collections Dependencies

By | September 17, 2022

In this article I will have a look at how to establish dependencies between Ansible collections so that if B has a dependency to C and A has a dependency to B then C will be available without when executing A without explicitly including C as a dependency to A.

Motivation

Dependencies between different Ansible collections.

When developing Ansible collections I want collection A to know as little as possible about collection B to which it has a dependency. This in order for changes in, for example, collection B concerning which collections it depends on not to necessitate changes in collection A.

Outline

The example of this article will consist of three different projects, each with its own git repository.

Prerequisites

The example in this article require Ansible and Molecule. Instructions on how to install Ansible are found here. Installation of Molecule is described here – install Molecule with the Docker driver.
Given that Molecule use the Docker driver, Docker is also required and need to be started in order to be able to execute Molecule tests.

To be able to establish dependencies between Ansible collections located in git repositories, SSH links are required in certain cases. This will require that you are able to connect to the repository using SSH. If you are using GitHub, please refer to this link concerning Connecting with SSH.

Project C

I’ll start with creating the C project that contains the Ansible collection C. It has no dependencies except that it uses ansible.builtin.

Create the Project

To create project C, do the following steps in a terminal window:

  • Create a directory named “ansible-collections-dependencies-c” and enter the directory.
mkdir ansible-collections-dependencies-c
cd ansible-collections-dependencies-c
ansible-galaxy collection init namespacec.collectionc
  • Move the “collectionc” directory located inside the “namespacec” director to the root of the project.
mv namespacec/collectionc .
  • Delete the “namespacec” directory.
rmdir namespacec
  • Create an Ansible configuration file named “ansible.cfg” in the root of the project with the following contents:
[defaults]
collections_paths = ./ansible_collections
roles_path = ./ansible_roles
  • Go to the “roles” directory of “collectionc”.
cd collectionc/roles/
  • Create a new role named “rolec” using Molecule.
molecule init role rolec --driver-name docker
  • Edit the file “rolec/tasks/main.yml” to contain the following:
---
# tasks file for rolec
- name: Log from C
  debug:
    msg: "Logging a message from Role C in Collection C"
  • Create a git repository named “ansible-collections-dependencies-c” and push the project to the repository.

I have used the ansible-galaxy and the molecule commands to create the new Ansible collection and role. This in order to have the standard collection and role layout with minimum effort. In addition, Molecule creates a default test for the new role.

Test the Project

To test the role contained in the project C using Molecule, open a terminal window located in the root directory of the project and then issue the following commands. Recall that Docker for Windows or for Mac has to be started in order for Molecule to be able to create container(s) mocking the remote hosts.

cd collectionc/roles/rolec
molecule test

The first time executing Molecule tests will take longer time due to the Docker image used to create containers mocking the remote hosts being pulled but subsequent runs will be faster.

The test-execution should complete without errors and, among other things, the following message should be output to the terminal console:

TASK [Include rolec] ***********************************************************

TASK [rolec : Log from C] ******************************************************
ok: [instance] => {
    "msg": "Logging a message from Role C in Collection C"
}

We did not actually implement any tests but rely on the default Molecule test that includes the role C.

My completed project c is located here: https://github.com/krizsan/ansible-collections-dependencies-c

Project B

The next project in the example, project B, will have a dependency to project C created earlier.

Create the Project

Project B is created by performing the following steps in a terminal window:

  • Create a directory named “ansible-collections-dependencies-b” and enter the directory.
mkdir ansible-collections-dependencies-b
cd ansible-collections-dependencies-b
ansible-galaxy collection init namespaceb.collectionb
  • Move the “collectionb” directory located inside the “namespaceb” director to the root of the project.
mv namespaceb/collectionb .
  • Delete the “namespaceb” directory.
rmdir namespaceb
  • Enter the directory “collectionb”.
cd collectionb
  • Edit the file named “galaxy.yml” and replace “dependencies: {}” with the following, establishing a dependency to project C.
    As far as a dependency to a git repositories is concerned, the first part is a SSH link to the repository and the second part is the name of the branch in the repository.
# In this location only SSH links to repositories have been successfully used.
dependencies: {
  'git@github.com:krizsan/ansible-collections-dependencies-c.git': 'main'
}
  • Create an Ansible configuration file named “ansible.cfg” in the root of the project with the following contents:
[defaults]
collections_paths = ./ansible_collections
roles_path = ./ansible_roles
  • Go to the “roles” directory of “collectionb”.
cd roles/
  • Create a new role named “roleb” using Molecule.
molecule init role roleb --driver-name docker
  • Edit the file “roleb/tasks/main.yml” to contain the following:
---
# tasks file for roleb
- name: Log from B
  debug:
    msg: "Logging a message from Role B in Collection B"
- name: Include role from Collection C
  include_role:
    name: namespacec.collectionc.rolec

Note that in the dependencies section of the galaxy.yml file the SSH link to the repository of project C is used. As far as I have been able to tell, HTTPS links to git repositories cannot be used in this location.

Test the Project

As with project C, we use Molecule to test the role. Again Docker for Windows or for Mac must be started in order for Molecule to be able to create container(s) that will be mocking the remote hosts.

cd collectionb/roles/roleb
molecule test

The test-execution should complete without errors and, among other things, the following text should be output to the terminal console:

TASK [Include roleb] ***********************************************************

TASK [roleb : Log from B] ******************************************************
ok: [instance] => {
    "msg": "Logging a message from Role B in Collection B"
}

TASK [Include role from Collection C] ******************************************

TASK [namespacec.collectionc.rolec : Log from C] *******************************
ok: [instance] => {
    "msg": "Logging a message from Role C in Collection C"
}

There is no verification of the outcome of role B in the default Molecule test but the above output shows that role B is executed and, as part of the execution, include role C.

The complete project can be found here: https://github.com/krizsan/ansible-collections-dependencies-b

Project A

The final project of the example is project A, which is the topmost project. As earlier, project A has a dependency to the Ansible collection “collectionb” in project B and no other dependencies.

  • Create a directory named “ansible-collections-dependencies-a” and enter the directory.
mkdir ansible-collections-dependencies-a
cd ansible-collections-dependencies-a
  • Create an Ansible configuration file named “ansible.cfg” in the root of the project with the following contents:
[defaults]
collections_paths = ./ansible_collections
roles_path = ./ansible_roles
  • Create an Ansible inventory file with the name “inventory.yml” with the following contents:
all:
  hosts:
    localhost:
  • Create an Ansible requirements file with the name “requirements.yml” establishing a dependency to the Ansible collection in project B:
---
collections:
  # In this location, an HTTPS as well as SSH link to the git repository may be used.
  # Using SSH will require an SSH key while HTTPS allow for anonymous access
  # provided that the repository is publicly available.
  - name: git@github.com:krizsan/ansible-collections-dependencies-b.git
    type: git
    version: master
  • Create an Ansible playbook file with the name “test-playbook.yml” that will include the role B from the collection B in the namespace B.
- hosts: localhost
  connection: local
  tasks:
    - name: Before
      debug:
        msg: "********** Before including role B"
    - name: Test including role B from collection B in namespace B
      include_role:
        name: namespaceb.collectionb.roleb
    - name: After
      debug:
        msg: "********** After including role B"

As in the comments of the requirements.yml file, both HTTPS as well as SSH links to the git repository from which the collection B is to be retrieved.

The complete project A can be found here: https://github.com/krizsan/ansible-collections-dependencies-a

Run the Example Playbook

The example projects are now complete and we can attempt to run the Ansible playbook, test-playbook, in project A.

Install Requirements

Before we can run the test playbook, the dependencies of the project containing the playbook need to be installed using the ansible-galaxy command:

ansible-galaxy install -r requirements.yml

You may be prompted for the password of your SSH key a few times as the installation process proceeds. After the installation has completed a directory named “ansible_collections” should appear in the root of project A. Recall that this is the name configured in the “ansible.cfg” file in the project earlier.
Examining the “ansible_collections” directory, using the “tree” command it has the following contents:

ansible_collections
├── namespaceb
│         └── collectionb
│             ├── FILES.json
│             ├── MANIFEST.json
│             ├── README.md
│             ├── plugins
│             │         └── README.md
│             └── roles
│                 └── roleb
│                     ├── README.md
│                     ├── defaults
│                     │         └── main.yml
│                     ├── handlers
│                     │         └── main.yml
│                     ├── meta
│                     │         └── main.yml
│                     ├── molecule
│                     │         └── default
│                     │             ├── converge.yml
│                     │             ├── molecule.yml
│                     │             └── verify.yml
│                     ├── tasks
│                     │         └── main.yml
│                     ├── tests
│                     │         ├── inventory
│                     │         └── test.yml
│                     └── vars
│                         └── main.yml
└── namespacec
    └── collectionc
        ├── FILES.json
        ├── MANIFEST.json
        ├── README.md
        ├── plugins
        │         └── README.md
        └── roles
            └── rolec
                ├── README.md
                ├── defaults
                │         └── main.yml
                ├── handlers
                │         └── main.yml
                ├── meta
                │         └── main.yml
                ├── molecule
                │         └── default
                │             ├── converge.yml
                │             ├── molecule.yml
                │             └── verify.yml
                ├── tasks
                │         └── main.yml
                ├── tests
                │         ├── inventory
                │         └── test.yml
                └── vars
                    └── main.yml

We can see that the ansible-galaxy command has downloaded both the direct dependency collectionb in namespaceb and also the indirect dependency collectionc in namespacec and made them available to the project.

Run the Playbook

With the dependencies in place, it should now be possible to run the test playbook. In a terminal window being located in the root of project A “ansible-collections-dependencies-a” directory, launch the test playbook with the following command:

ansible-playbook -i inventory.yml test-playbook.yml

Output similar to the following should appear in the terminal window:

PLAY [localhost] ***************************************************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Before] ******************************************************************
ok: [localhost] => {
    "msg": "********** Before including role B"
}

TASK [Test including role B from collection B in namespace B] ******************

TASK [namespaceb.collectionb.roleb : Log from B] *******************************
ok: [localhost] => {
    "msg": "Logging a message from Role B in Collection B"
}

TASK [Include role from Collection C] ******************************************

TASK [namespacec.collectionc.rolec : Log from C] *******************************
ok: [localhost] => {
    "msg": "Logging a message from Role C in Collection C"
}

TASK [After] *******************************************************************
ok: [localhost] => {
    "msg": "********** After including role B"
}

PLAY RECAP *********************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0 skipped=0    rescued=0    ignored=0   

Note that:

  • A message “Before including role B” is logged.
    This message is logged from the test playbook contained in the “test-playbook.yml” file.
  • A message “Logging a message from Role B in Collection B” is logged.
    As the message states, it is logged by role B in collection B. The log statement is contained in the file “main.yml” located in the “tasks” directory, which in turn is located in the “roles/roleb” directory in the “collectionb” directory of project B.
    The reason for this message being logged is of course that the test playbook included the role in question.
  • A message “Logging a message from Role C in Collection C” is logged.
    This message is logged by role C in collection C. This log statement is contained in the file “main.yml” located in the “tasks” directory, which in turn is located in the “roles/rolec” directory in the “collectionc” directory of project C.
    The role C is included by the role B without the test playbook or the project containing the test playbook (project A) knowing anything about and having no direct dependencies to the role C or the project containing it (project C).
  • Finally, a message “After including role B” is logged.
    This message is logged from the test playbook in project A.

We have successfully developed a project, project B, that contains a collection that has a dependency to another project, project C. In addition we have used project B from another project, project A, that does not need to have any knowledge of the dependencies of project B.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *