Ansible Collection Packaging Guidelines

Forward

Ansible collections are packaged units of Ansible content, including modules and other types of plugins. Most Ansible Plugins are written in Python or Powershell.

Some collections are also included in Ansible Community’s ansible collection bundle, which is packaged in Fedora. All collections, whether or not they are included in the ansible package, MAY be packaged in Fedora.

ansible depends on ansible-core, which contains the core engine and CLI programs (e.g. ansible, ansible-playbook). The ansible package has a different release cycle than individual collections, and it may contain older versions of the individual components. ansible installs collections in a different namespace and is parallel installable with individual collections. The Ansible engine searches for collections in the standalone collections directory first.

See Changes/Ansible5 for more information about the split between ansible and ansible-core.

Nommage

Collection packages MUST be named ansible-collection-NAMESPACE-NAME. For example, the community.general collection package is named ansible-collection-community-general.

Collection Source

Collection source code MUST be downloaded from the collection’s respective Git forge/other SCM repository. While the tarballs published to Ansible Galaxy contain all of the collection’s Python/Powershell source code as well as some development files, they do not include the galaxy.yml build configuration and development files (e.g. unit tests) that the author may choose to remove. Note that the Ansible Community collection requirements mandate that collections tag releases in a public SCM repository.

Collection packages SHOULD use %{ansible_collection_url NAMESPACE NAME} as the package’s URL:. This points to the collection’s homepage on Ansible Galaxy.

Dependencies

Buildtime

Collections MUST have BuildRequires: ansible-packaging. ansible-packaging provides macros and a dependency generator for packaging Ansible Collections. It also pulls in ansible-core, so BuildRequires: ansible-core SHOULD NOT be added manually.

Runtime

The dependency generator will generate the appropriate dependency on the Ansible engine. This ensures compatibility with Fedora 35 which contains the classic ansible 2.9 package (instead of the collections bundle) and ansible-core. Both versions of the Ansible engine support collections, but they are not parallel installable. Packages MUST NOT manually Require ansible-core or ansible, unless they are known to require a specific version, in which case the appropriate version constraints should be used.

The dependency generator also handles inter-collection dependencies.

External dependencies of plugins

Ansible collections may contain various plugins that have various external dependencies. The Ansible dev guide mandates that plugins fail cleanly if these dependencies are not installed. Many times, external dependencies are only needed for a small subset of the collection which may or may not be widely used. Therefore, collection packages SHOULD weakly depend on these external libraries, i.e. use Recommends instead of Requires.

Module dependencies are only needed on the target node not the controller node. Therefore, collection packages SHOULD NOT depend on these dependencies at all, weakly or strongly. Users are responsible for installing these dependencies on the target host. Modules that are intended to be used with delegate_to: localhost are an exception to this rule.

The situation is a bit different for controller plugins, such as filter plugins, lookup plugins, connection plugins, or inventory plugins. Collections MAY add Recommends for dependencies of controller plugins. However, packagers should use discretion when adding any type of dependency and only do so when it is required for the central functionality of the collection. For instance, it makes sense for ansible-collection-community-docker to Recommend python3-docker, but it doesn’t make sense for the larger, more general ansible-collection-community-general collection to Recommend python3-redis for the redis lookup plugin. This guideline seeks to prevent ballooning collection packages. ansible-core and ansible follow this same principle.

Build and Installation

To build the collection artifact, packages MUST use %ansible_collection_build in %build. %ansible_collection_install MUST be used in %install to install the artifact.

Packagers SHOULD use %files -f %{ansible_collection_filelist} to install the collection. The %{ansible_collection_filelist} is populated by %ansible_collection_install.

Unit Tests

As per the general Fedora Packaging Guidelines, collection packages SHOULD run upstream unit tests in %check if practical. Integration tests are impossible to run in the RPM build environment. In order to run unit tests, collections MUST BuildRequire ansible-packaging-tests, which pulls in the necessary dependencies. Some collections have other testing dependencies, which are usually specified in tests/unit/requirements.txt. These have to be added manually. The %ansible_test_unit macro MUST be used to run tests.

EPEL Compatibility

It is currently impossible to run unit tests on EPEL 8 and 9.

ansible-core in RHEL 8 and 9 are built against alternative python stacks for which the necessary test dependencies are not available.

The rest of these guidelines are applicable to EPEL 8 and 9, and ansible-packaging itself is available there.

Unnecessary Files

By default, collections ship with all of the files in the repository root, unless they are manually excluded. Therefore, many collections contain development files that are unwanted by users.

Packagers SHOULD exclude these files, which SHOULD be done by patching the collection’s galaxy.yml to add these files to the build_ignore configuration. These files SHOULD NOT be removed with rm. See the Ansible documentation for more information on the galaxy.yml syntax.

Common development files include:

  • The tests directory containing unit and integration tests

  • SCM configuration such as .gitignore and .keep files

  • The .azure-pipelines and .github directories that contain CI configuration

These files often have to be removed downstream, as there are some unresolved issues with pushing these changes to upstream community collections. These issues are irrelevant in the Fedora context.

Shebangs

Ansible plugins are not executable. However, many of them have #!/usr/bin/python shebangs for legacy reasons. These shebangs MUST be removed for the following reasons:

  1. Non-executable files shouldn’t have shebangs

  2. Keeping the shebangs results in an unnecessary dependency on python-unversioned-command.

%py3_shebang_fix MUST NOT be used, as it will break compatibility with certain Ansible target nodes. It won’t fix the non-executable file issue, either.

Shebangs can be removed with:

find -type f ! -executable -name '*.py' -print -exec sed -i -e '1{\@^#!.*@d}' '{}' +

Documentation and License Files

License files and documentation for collections are installed to the collection’s directory in /usr/share/ansible, by default. Packagers MAY choose to either mark the license and documentation files in this directory with %license and %doc or to add the correct paths to build_ignore in galaxy.yml and install them into the standard directories. Avoid duplicating these files in both places.

Note that some multi-licensed collections store licenses in a LICENSES directory. This whole directory MUST be marked with %license.

Refer to the Legal docs for the rules about allowed licenses and determining the License: field.

Example Specfile

# Only run tests where the dependencies are available
%if %{defined fedora}
%bcond_without tests
%else
%bcond_with tests
%endif

Name:           ansible-collection-community-rabbitmq
Version:        1.2.2
Release:        1%{?dist}
Summary:        RabbitMQ collection for Ansible

# plugins/module_utils/_version.py: Python Software Foundation License version 2
License:        GPL-3.0-or-later and PSF-2.0
URL:            %{ansible_collection_url community rabbitmq}
%global forgeurl https://github.com/ansible-collections/community.rabbitmq
Source0:        %{forgeurl}/archive/%{version}/%{name}-%{version}.tar.gz
# Patch galaxy.yml to exclude unnecessary files from the built collection.
# This is a downstream only patch.
Patch0:         build_ignore.patch

BuildRequires:  ansible-packaging
%if %{with tests}
BuildRequires:  ansible-packaging-tests
# Collection specific test dependency
BuildRequires:  glibc-all-langpacks
%endif

BuildArch:      noarch

%description
%{summary}.


%prep
%autosetup -n community.rabbitmq-%{version} -p1
find -type f ! -executable -name '*.py' -print -exec sed -i -e '1{\@^#!.*@d}' '{}' +


%build
%ansible_collection_build


%install
%ansible_collection_install


%if %{with tests}
%check
%ansible_test_unit
%endif


%files -f %{ansible_collection_filelist}
%license COPYING PSF-license.txt
%doc README.md CHANGELOG.rst

%changelog

build_ignore.patch:

diff --git a/galaxy.yml b/galaxy.yml
index 0b37162..acd029a 100644
--- a/galaxy.yml
+++ b/galaxy.yml
@@ -13,3 +13,13 @@ repository: https://github.com/ansible-collections/community.rabbitmq
 documentation: https://docs.ansible.com/ansible/latest/collections/community/rabbitmq/
 homepage: https://github.com/ansible-collections/community.rabbitmq
 issues: https://github.com/ansible-collections/community.rabbitmq/issues
+build_ignore:
+  # Remove unnecessary development files from the built package.
+  - tests
+  - .azure-pipelines
+  - .gitignore
+  # Licenses and docs are installed with %%doc and %%license
+  - PSF-license.txt
+  - COPYING
+  - README.md
+  - CHANGELOG.rst

Macro Breakdown

Here is a short breakdown of exactly what each macro included in ansible-packaging does.

%ansible_collection_url

Usage:

URL:            %{ansible_collection_url NAMESPACE NAME}

This macro points to a collection’s Ansible Galaxy page. It is intended to be used for the URL: tag in the specfile preamble. It takes the collection namespace and collection name as arguments.

If no arguments are passed to this macro, it falls back to the values of %{collection_namespace} and %{collection_name} if they are set in the specfile. New packages SHOULD explicitly pass the namespace and name as arguments. The fallback may be removed in the future. See the Legacy Macros section for more information.

%ansible_collection_build

Usage:

%build
%ansible_collection_build

This macro simply runs ansible-galaxy collection build.

%ansible_collection_install

Usage:

%install
%ansible_collection_install

This macro pulls out the collection namespace, name, and version from galaxy.yml and then uses it to run ansible-galaxy collection install. After that, it writes out %{ansible_collection_filelist} based on the metadata it previously extracted

%ansible_test_unit

Usage:

%check
%ansible_test_unit

This macro parses galaxy.yml to determine the collection namespace and name that’s needed to create the directory structure that ansible-test expects. After creating a temporary build directory with the needed structure, the script runs ansible-test units with the provided arguments.

%{ansible_collection_filelist}

Usage:

%files -f %{ansible_collection_filelist}
%doc ...
%license ...

This macro points a file list that’s written out by %ansible_collection_install. Currently, it only contains a single entry to own the collection’s entire directory in %{ansible_collections_dir}

This macro expands to %{_datadir}/ansible/collections/ansible_collections. It is used internally by the other macros. Packagers are expected to use %ansible_collection_install and %ansible_collection_filelist instead of directly referencing this directory.

Legacy macros

%{collection_namepsace}

Usage:

%global collection_namespace NAMESPACE

The ansible-packaging macros previously required packagers to manually set %collection_namespace in specfiles. Now, the macros extract the collection namespace from the galaxy.yml.

%{collection_name}

Usage:

%global collection_name NAME

The ansible-packaging macros previously required packagers to manually set %collection_name in specfiles. Now, the macros extract the collection name from the galaxy.yml.

%{ansible_collection_files}

Usage:

%files
%doc ...
%license ...
%{ansible_collection_files}

New specfiles should use %files -f %{ansible_collection_filelist} instead of this macro. %{ansible_collection_files} requires setting %collection_namespace and %collection_name.