I had a draft of this article for about two months, but never really managed to polish and finalize it, partially due to some nasty hacks needed down the road. Thankfully, one of my wishes was heard and I had now the chance to revisit the post and try a few things out. Sadly, my wish was granted only partially and the result is still not beautiful, but read yourself ;-)
UPDATE: I've published a follow up post on
building documentation for Ansible Collections using antsibull, as my wish was now fully granted.
As part of my day job, I am maintaining the
Foreman Ansible Modules - a collection of modules to interact with Foreman and its plugins (most notably Katello). We've been maintaining this collection (as in set of modules) since 2017, so much longer than collections (as in Ansible Collections) existed, but the introduction of Ansible Collections allowed us to provide a much easier and supported way to distribute the modules to our users.
Now users usually want two things: features and documentation. Features are easy, we already have plenty of them. But documentation was a bit cumbersome: we had documentation inside the modules, so you could read it via
ansible-doc on the command line if you had the collection installed, but we wanted to provide online readable and versioned documentation too - something the users are used to from the official Ansible documentation.
Building HTML from Ansible modules
Ansible modules contain
documentation in form of YAML blocks documenting the parameters, examples and return values of the module. The Ansible documentation site is built using
Sphinx from reStructuredText. As the modules don't contain reStructuredText, Ansible
hashad a tool to generate it from the documentation YAML:
build-ansible.py document-plugins. The tool and the accompanying libraries are not part of the Ansible distribution - they just live in the
hacking directory. To run them we need a git checkout of Ansible and source
hacking/env-setup to set
PYTHONPATH and a few other variables correctly for Ansible to run directly from that checkout.
It would be nice if that'd be a feature of ansible-doc, but while it isn't, we need to have a full Ansible git checkout to be able to continue.The tool has been recently split out into an own repository/distribution:
antsibull. However it was also a bit redesigned to be easier to use (good!), and my hack to abuse it to build documentation for out-of-tree modules doesn't work anymore (bad!). There is an
issue open for collections support, so I hope to be able to switch to
antsibull soon.
Anyways, back to the original hack.
As we're using documentation fragments, we need to tell the tool to look for these, because otherwise we'd get errors about not found fragments.
We're passing
ANSIBLE_COLLECTIONS_PATHS so that the tool can find the correct, namespaced documentation fragments there.
We also need to provide
--module-dir pointing at the actual modules we want to build documentation for.
ANSIBLEGIT=/path/to/ansible.git
source $ ANSIBLEGIT /hacking/env-setup
ANSIBLE_COLLECTIONS_PATHS=../build/collections python3 $ ANSIBLEGIT /hacking/build-ansible.py document-plugins --module-dir ../plugins/modules --template-dir ./_templates --template-dir $ ANSIBLEGIT /docs/templates --type rst --output-dir ./modules/
Ideally, when
antsibull supports collections, this will become
antsibull-docs collection without any need to have an Ansible checkout, sourcing
env-setup or pass tons of paths.
Until then we have a
Makefile that clones Ansible, runs the above command and then calls Sphinx (which provides a nice
Makefile for building) to generate HTML from the reStructuredText.
You can find our slightly modified templates and themes in our
git repository in the docs directory.
Publishing HTML documentation for Ansible Modules
Now that we have a way to build the documentation, let's also automate publishing, because nothing is worse than out-of-date documentation!
We're using GitHub and GitHub Actions for that, but you can achieve the same with GitLab, TravisCI or Jenkins.
First, we need a trigger. As we want always up-to-date documentation for the main branch where all the development happens and also documentation for all stable releases that are tagged (we use
vX.Y.Z for the tags), we can do something like this:
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
branches:
- master
Now that we have a trigger, we define the job steps that get executed:
steps:
- name: Check out the code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.7"
- name: Install dependencies
run: make doc-setup
- name: Build docs
run: make doc
At this point we will have the docs built by
make doc in the
docs/_build/html directory, but not published anywhere yet.
As we're using GitHub anyways, we can also use GitHub Pages to host the result.
- uses: actions/checkout@v2
- name: configure git
run:
git config user.name "$ GITHUB_ACTOR "
git config user.email "$ GITHUB_ACTOR @bots.github.com"
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.7"
- name: Install dependencies
run: make doc-setup
- name: Build docs
run: make doc
- name: commit docs
run:
git checkout gh-pages
rm -rf $(basename $ GITHUB_REF )
mv docs/_build/html $(basename $ GITHUB_REF )
dirname */index.html sort --version-sort xargs -I@@ -n1 echo '<div><a href="@@/"><p>@@</p></a></div>' >> index.html
git add $(basename $ GITHUB_REF ) index.html
git commit -m "update docs for $(basename $ GITHUB_REF )" true
- name: push docs
run: git push origin gh-pages
As this is not exactly self explanatory:
- Configure git to have a proper author name and email, as otherwise you get ugly history and maybe even failing commits
- Fetch all branch names, as the checkout action by default doesn't do this.
- Setup Python, Sphinx, Ansible etc.
- Build the documentation as described above.
- Switch to the
gh-pages branch from the commit that triggered the workflow.
- Remove any existing documentation for this tag/branch (
$GITHUB_REF contains the name which triggered the workflow) if it exists already.
- Move the previously built documentation from the Sphinx output directory to a directory named after the current target.
- Generate a simple index of all available documentation versions.
- Commit all changes, but don't fail if there is nothing to commit.
- Push to the
gh-pages branch which will trigger a GitHub Pages deployment.
Pretty sure this won't win any beauty contest for scripting and automation, but it gets the job done and nobody on the team has to remember to update the documentation anymore.
You can see the results on
theforeman.org or directly on
GitHub.