It is a well-known fact that real programmers do not document their code. If it was hard to write, it should be hard to understand and use. But this attitude might prove itself to be a bit problematic if we would like to get paid for our work. And because we do not want to see you all broke, we will show you how to document your Ansible collections using Sphinx.
If you would like to follow along and test the commands from this post on your computer, you will need to create a new virtual environment and clone the Sensu Go Ansible collection like this:
$ python3 -m venv venv $ . venv/bin/activate (venv) $ mkdir -p ansible_collections/sensu (venv) $ cd ansible_collections/sensu (venv) $ git clone https://github.com/sensu/sensu-go-ansible.git sensu_go (venv) $ cd sensu_go (venv) $ rm -rf docs/*
And yes, all those directories are needed. We will (probably) explain the reason behind this requirement in another post. But now it is time to get back to the problem of documenting the Ansible collections.
One, two... eight documentation types!?!
For this post, we will clump together most of the types and only consider two kinds of documentation: the API specification that is part of the source code, and the rest of the guides that we ship with our Ansible collection. Why? Because we can extract the API documentation from the source code. We need to write the rest of the documentation manually.
We will only briefly look at the writing process itself and focus instead on more technical aspects of the documentation authoring:
- What documentation tools do we need to install?
- How can we set up the initial structure of the documentation directory?
- What steps do we need to perform when rendering the documentation?
- How can we publish the collection documentation online?
So let us get right into the bootstrapping process.
Tools of the trade
We will be using two tools to prepare the documentation for our collection: ansible-doc-extractor for extracting the documentation embedded in Ansible modules, and Sphinx for driving the rendering process.
You have not heard about
ansible-doc-extractoryet? It is probably the best invention since sliced bread ;) What does it do? It can extract module API documentation and render it into reStructuredText documents. We created it because we are lazy and need something that allows us to create a web API documentation for Sensu Go Ansible collection modules. And now you all get to benefit from our laziness ;)
Since both packages are available on Python package index, we can install them with a single command:
(venv) $ pip install Sphinx ansible-doc-extractor
If everything went according to plans, we now have
ansible-doc-extractor commands available and can start creating the documentation directory structure.
The documentation structure
Our Ansible collection documentation will live in the
docs top-level directory. This directory is where we will place all of the Sphinx configuration, where our source documentation will live, and where the Sphinx will output the rendered documents.
Once we have created the
docs directory, we can initialize the Sphinx configuration by running:
(venv) $ ( cd docs && sphinx-quickstart )
Make sure you answer
y when asked about whether to separate the
build directories. Having those directories split will help us keep the documentation source files tidy and neat. Feel free to answer all other questions to your liking.
Now comes the best part: writing and generating the actual documentation ;)
Preparing documentation sources
If we want our documentation to be of any use for the end-user, we should probably create at least three sections:
- A short quickstart chapter is a must in today's world where time is money.
- An article or two that describe common scenarios that our Ansible collection is designed to cover.
- A generated module API docs that end-users will use when writing playbooks.
We will need to write the first two sections manually - there is no way around it. But as already said before, we can automate that last part.
Let us assume that we have our API documentation files stored in the
docs/source/modules directory. Then we can include all of them into our documentation by modifying the table of contents directive in the
docs/source/index.rst document to:
.. toctree:: :maxdepth: 1 :caption: Contents: :glob: modules/*
Now, all we need to do is extract the module API documentation and place it inside the
docs/source/modules directory. And thanks to
ansible-doc-extractor, this is almost trivial. For example, extracting the API documentation from the
sensu.sensu_go.asset module is as easy as running:
(venv) $ mkdir -p docs/source/modules (venv) $ ANSIBLE_COLLECTIONS_PATHS=$(pwd)/../../.. \ ansible-doc-extractor docs/source/modules plugins/modules/asset.py
Once the previous command finishes, we should have the documentation for our asset module safely stored in the
You might have noticed that
ANSIBLE_COLLECTIONS_PATHSvariable that we set when running the extraction process. Why do we need it? Because Ansible needs to include Python modules that contain document fragments. And it can only do so if we point it towards the right folder. Oh, by the way, this is why we created all those folders when we cloned the Sensu Go collection.
Now for the grand finale: rendering the documentation and publishing it to the web.
Publishing the docs
We will be using GitHub Pages to host our documentation for the sake of simplicity. You can, of course, use just about anything that can serve a static web page.
But before we can push our content to the GitHub, we need to render it. Thankfully, the
sphinx-quickstart program created a Makefile in the
docs folder that contains all the commands we need. All we need to run is:
(venv) $ ( cd docs && make html )
If we now open the
docs/build/html/index.html file with the web browser of our choice, we should see the home page of our documentation:
(venv) $ xdg-open docs/build/html/index.html
The last thing we need to do is upload the final documentation to the GitHub. If you need help with this, feel free to visit the GitHub Pages site and browse the documentation. For the rest of you, here is the autopilot version of the first-time publish:
(venv) $ git checkout --orphan gh-pages (venv) $ git rm -rf . (venv) $ touch .nojekyll (venv) $ git add .nojekyll (venv) $ git commit -m "Init gh-pages branch" (venv) $ git checkout master (venv) $ git worktree add gh-pages gh-pages (venv) $ rm -rf docs/build (venv) $ ( cd docs && make html ) (venv) $ rsync -av docs/build/html/ gh-pages/ (venv) $ cd gh-pages (venv) $ git commit -am "Update docs for release x.y.z" (venv) $ git push -u origin gh-pages (venv) $ cd ..
Subsequent documentation updates are substantially more straightforward:
(venv) $ rm -rf docs/build (venv) $ ( cd docs && make html ) (venv) $ rsync -av docs/build/html/ gh-pages/ (venv) $ cd gh-pages (venv) $ git commit -am "Update docs for release x.y.z" (venv) $ git push (venv) $ cd ..
You are welcome ;)
Some of you might have noticed that the generated documentation currently looks like crap. And we cannot blame you ;) Keep in mind that
ansible-doc-extractor is in its early stages of evolution. Once we verify the concept, we will kindly ask a designer or two to help us get things in better shape. But until then, enjoy the eye-watering beauty of "vim is all I need"-Esque design ;)
If you need a real-world example of an Ansible collection with documentation machinery all set up, head over to the Sensu Go Ansible collection repository and look into the
docs directory. And as always, feel free to contact us on Twitter or Reddit.