- Role syntax check
- Role linter implementing best practices
- Packaging & publishing into private web (DAV) repositories
tasks/main.ymlgeneration, with platforms runtime check
The following build targets are supported:
initinstantiate role template
showshow role information
distgenerate ansible distributable role files
cleandelete all generated files
checkinclude role in a dummy playbook and check syntax
publishpublish role to a web repository
The following lifecycles are supported:
Ansible-universe uses the ansible-galaxy build manifest (
meta/main.yml) with extra attributes:
prefix, variable prefix, defaults to rolename
version, defaults to 0.0.1
variables, maps names to descriptions
tasks/filenames to include conditions
usage_complement, optional, additional usage instructions
maintenance_complement, optional, additional maintenance instructions
For this tutorial, we're going to work on a simple use-case:
a provisioner for a
A role is a directory containing various assets, the first step is therefore to create that directory:
$ mkdir nginx
Let's initialize the role with Ansible-universe:
$ ansible-universe -C nginx init
init target creates a dummy (ansible-galaxy) role manifest:
This manifest is also the build manifest for Ansible-universe.
This is actually the only required file for distributing a role.
Take some time to edit this file:
- set the author (you)
- set a description
- select supported platforms (Debian and Ubuntu in our case)
You are then free to fill-in the other directories depending on your role. Remember only 8 sub-directories are specified, for further details, please check the Directory Layout section of the best practices, in the appendix.
As for this tutorial, we only need the
This directory contains a single file for now, named
$ cat > tasks/nginx.yml <<EOF --- - apt: name: nginx state: present - service: name: nginx state: started enabled: yes - EOF
Let's call Ansible-universe to generate and check everything:
$ ansible-universe -C nginx check generating nginx/tasks/main.yml generating nginx/README.md playbook: playbook.yml ERROR: expecting dict; got: None, error in /tmp/nginx/tasks/nginx.yml ** WARNING: syntax error source: role 'nginx' flag: syntax ** WARNING: missing 'name' attribute, please describe the target state source: task 'nginx.yml[#2]' flag: task_has_name
As indicated in the
lifecycle section, the
check target implies
dist, which is called first.
dist, two files are generated:
tasks/main.yml, performing the platform check and including any other YAML file in
tasks/. Conditions to inclusions can be specified via the
include_whenattribute of the manifest.
README.md, gathering the role description, supported platforms and data on variables.
check, all checks are run, and in the above example, 2 warnings were raised.
The fact that ERROR is printed and then WARNING is a tad confusing.
What's happening is that the
ansible-playbook subprocess is terminating on an error and print it.
This is caught by Ansible-universe as a more generic warning stating that the syntax is incorrect.
A syntax error was detected, let's fix it by removing the last dash in
The other warning says that we didn't describe one of our tasks, add a name attribute to fix it.
Re-run Ansible-universe, you should get the following layout with no warning:
$ tree nginx/ nginx/ ├── meta │ └── main.yml ├── README.md └── tasks ├── main.yml └── nginx.yml
Your role is now ready to be distributed.
If you're using a VCS as repository, simply commit and push the files,
but remember to exclude (e.g. in
.gitignore) the build byproducts (
If you're using a web repository, proceed as follow (set a working repository URL beforehand):
$ ansible-universe -C nginx publish -r http://somewhere generating nginx/.build/nginx-0.0.1.tgz ./README.md ./meta/main.yml ./tasks/main.yml ./tasks/nginx.yml publishing nginx/.build/nginx-0.0.1.tgz to http://somewhere
$ pip install --user ansible-universe
$ pip uninstall ansible-universe
The built-in linter can easily be extended with your own checks:
- in the universe directory, create a new module defining the
__init__.py, register that new module by its name in the
In your module, the
MANIFEST dictionary shall contains the following attributes:
task. This specify the objects that will be passed to the predicate below.
predicate: a callback with two parameters (object, helpers) and returning a Boolean.
helpersis a dictionary containing various functions and objects:
message: the message to display when the predicate is violated.
flag: default to module name, symbol used with the
-Wcommand line option.
Ansible Best Practices
Always assume your playbook users are not developers. Design your playbooks to be configurable through groups and variables. Users will use those in the inventory and varfiles. The inventory and varfiles are expected to be created/edited, but making your user modify a playbook is a design mistake.
5 sub-directories are specified for a playbook:
If you need additional assets, such as files or templates, then define a role to manage them.
Roles specific to your playbook have to be shipped with it.
3rd-parties dependencies have to be registered into the
and resolved by
If you need a piece of provisioning more than once, re-design it as a role. Roles are to Ansible what packages are to your platform or programming language.
You can safely assume that role users are actually developers, as using them requires some more advanced Ansible skills. Design your roles to be configurable through variables.
Make your roles available either on a public VCS or on a public web repository. Prefer web repository to avoid access control issues. Indeed, contrary to other modern build stacks (e.g. Java, Python, Docker...), Ansible does not offer any kind of package repository; instead roles are simply “published” as code repositories (e.g. on github, etc.) This entails major issues:
- Unexpected Access Restrictions Code repositories are designed to enforce fine grained access control from the ground up; this means you have to explicitly be given access to a repository to use it. Package repositories have the opposite behavior: once published, a package is accessible to anyone (e.g. apt-get install, pip install...) except if configured otherwise (opt-in.) The intent is clear: on publication, the package is made available to everyone. Therefore, using a code repository as a package repository violates the UX principle of least surprise.
No Version Ordering & Expressions
A VCS uses an internal versioning system (e.g. a hash for git)
which is not related to the “human readable” version strings (e.g. “v1.0.0”.)
Version strings are generally tag names created manually by developers when necessary.
As version strings are unusable by git and by extension, by ansible-galaxy,
version expressions cannot be used when specifying a dependency.
With other build tools, it is common for instance to request an compatible version of a dependency
within a range and exclude a subset of releases known as broken, e.g.
>=2.1, <4a0, !=2.4rc, != 2.5rc
- Inefficient Binary Resources Storage A playbook may contain binary resources (e.g. images or pre-compiled bytecode.) Storing those resources into a code repository is a bad practice (REF?.)
3rd-parties dependencies have to be registered into the
dependencies attribute of the role manifest.
Keep roles self-contained. Having shared variables between two roles is a design mistake. Instead make your roles configurable via variables and handle integration issues in the corresponding play.
The following requirements are all validated by default via the
$ cd myrole $ ansible-universe check
You can switch on only the ones you're interested in with the
Make sure your role manifest contains the required information for publication:
- Version (extended attribute),
- Supported platforms
Given a playbook or a role, if groups or variables are not documented, they are non-existent as,
unfortunately, Ansible (as of version 1.9.2) has no native mechanism to probe them.
The documentation (generally the
README.md file) is therefore the only learning medium for the end-users.
Make sure your documentation it is up-to-date.
Prefix (bis, rebis) all your playbook groups, playbook variables and role variables by a short and unique ID. Ideally the playbook name if it fits. Ansible only has a global namespace and having two identical variables will lead one to be overwritten by the other. This is also true for handler names.
Do not add any custom sub-directory to a role or playbook, this would lead to undefined behavior: at any point in a future version, other sub-directories might be needed by Ansible and if they are already used by your role for anything else, this will break.
As of Ansible version 1.9.2, 8 sub-directories are specified for a role:
Make the intent of each task explicit by setting its
Do not set a Remote User,
It's tempting to always assume your playbooks/roles are run as root and to enforce it by setting
This is a bad idea as your users might want to use another user that has equivalent privileges (e.g. via sudo.)
The root account is often disabled on modern systems for security reasons.
If you need an explicit user for a given task, use
sudo_user: <name> and
On copy/template, Set a owner,
If no owner is specified on a
copy or a
template task, the current user UID will be used.
But that user can change and so will the file owner depending on who is running the playbook.
This violates the idempotence rule.