As I outlined in the first entry, this entire site is generated using ansible templates and a simple playbook. For now, it fits the purpose surprisingly well. Sure, I can't neatly write just a bunch of .md-files and sprinkle some metadata on top, but I don't think I'd need to. Being honest, I'll most likely update this blog every few months at most, so small burden from having to repeat myself and essentially write these pages in pure HTML is not that significant.
In this blog post, I'll go through the setup and list down the main considerations behind it. I'll explain the deployment model and automation in a later post.
Simply put, all content is stored and versioned as templates in a directory structure like this:
$ tree templates templates |-- blog | |-- blog-generation.j2 | |-- first-entry.j2 |-- blog.html.j2 |-- footer.j2 |-- header.j2 |-- index.html.j2 |-- list.html.j2 |-- onnilampi.fi.container.j2
The template directory also contains a template for managing the site container, but as said, more on that in a later post.
Templates are utilized by a simple Ansible playbook, which first generates a directory in the site hierarchy, and then places an index-file in that directory. Here is an example of how the blog posts are handled:
- name: Generate directories for blog entries ansible.builtin.file: path: "./site/blog/{{ item }}" state: directory mode: '0755' with_items: - first-entry - blog-generation - name: Generate blog entry ansible.builtin.template: src: "blog/{{ item }}.j2" dest: "./site/blog/{{ item }}/index.html" mode: '0644' validate: "tidy -eq %s" with_items: - first-entry - blog-generation
The tasks above simply loop through all the declared templates, output .html-files, and use tidy to validate the output. This ensures that all output is valid HTML, and weird templating or formatting errors are caught early on.
The implementation has a tiny trap in the sense that all new blog posts need to be updated in three places: in two Ansible tasks and one static HTML-file. There really is no way around this as long as I want to keep the setup light, but luckily the potential damage of this is relatively easy to mitigate. In total, there are 8 different combinations of what can happen:
Directory task | Template task | Blog index | Outcome |
---|---|---|---|
UPDATED | UPDATED | UPDATED | All works as expected |
UPDATED | UPDATED | NOT UPDATED | Hidden entry is generated |
UPDATED | NOT UPDATED | UPDATED | Empty directory created |
UPDATED | NOT UPDATED | NOT UPDATED | Empty directory created |
NOT UPDATED | UPDATED | UPDATED | Templating will fail |
NOT UPDATED | UPDATED | NOT UPDATED | Templating will fail |
NOT UPDATED | NOT UPDATED | UPDATED | Broken link is added to index |
NOT UPDATED | NOT UPDATED | NOT UPDATED | Nothing happens |
As seen in the table, several different situations can happen. 3/8 are non-intrusive, so those don't require any action. The two failures are desired, and the other three should preferably also fail. As I'm a lazy person, I simply implemented a task that fails if there are empty directories:
- name: Check that there are no empty directories ansible.builtin.command: argv: - find - ./site - -empty - -type - d register: empty_dirs changed_when: false failed_when: "empty_dirs.stdout_lines | length != 0"
Down the line, I might write an ansible module to check for the broken links. For now, I'll just accept the fact that it is possible to add broken links into the blog index. The two remaining outcomes of creating hidden entries and doing nothing are considered features for now.
I'm relatively happy with the result, to be honest. Most of the requirements are fulfilled quite well, and this current implementation contains a minimal amount of code. There are also several interesting opportunities for future development work when it comes to generating a static site with Ansible. Things such as automatically checking for broken links and generating an RSS feed are on the roadmap.
Stay tuned for part 2, where I'll cover all the deployment tooling and infrastructure!