There are many steps to take on the path to automation. Some are easier, some not so much.

One less popular step is documentation. Yes - the documentation you haven’t written. You know what I mean.

No matter how well written your code is, there usually is a gap between today’s YOU and the future YOU, who has to work with and pick up what you leave behind.

This is usually also the reason for writing the documentation last. Better to have something than nothing, right?

But why not automate that part of the documentation away, which can be automated?

Currently I am writing a good deal of code for a customer. Mainly Terraform code this is.

Partly I am working with inherited code and partly I am creating new processes and code.

However, there’s seldom a process for writing documentation (aka. Readme-files) on the customer sides in place. The end result in term of documentation therefore lasts from a single header line up to one or two paragraphs maximum in the repositories Readme file.

Occasionally you find some gold nuggets: Multiple pages covering almost every desirable aspect. That happened once.

To be honest: The bar usually is very low.

For Terraform - a provisioning and deployment tool for infrastructure - there is a little helper tool called terraform-docs that can take some parts of documenting tedious parameters and structures.

Here is a possible way of how to use it to automate the documentation process and at the same time achieve quite acceptable results.


To understand what it does and how it does it, you need to know a bit about Terraform.

  1. Terraform code is highly structured and follows a strict guideline how you define your resources and variables.
  2. All variables must be defined and are using an unambiguous syntax.
  3. Whatever you want to provision with Terraform, requires a so called provider, that also is clearly defined.
  4. Whatever information you want to output, requires a so called output, which is - again - clearly defined.
  5. It doesn’t matter where you write the code, everything is handled as if it would be one big file anyway. Splitting up the code into files is only necessary for the wet-ware/humans.

That outlines the overall structure and elements for using a Terraform workspace (or even a module for that matter). Dependencies and other implicit information are not as easy to identify. The details on how the code works are unfortunately often not outlined in the Readme’s in any greater detail. Those bits and pieces are left scattered in the code for people with a “special interest”. But this information is certainly not necessary to be able to use the code.

And people who are interested in how to use the code are the kind of audience Readme’s are trying to address.

Terraform-docs takes advantage of all that and simply parses the code files for:

  • Variable definitions,
  • Provider definitions,
  • Requirement definitions and
  • Output definitions.

As a result the documentation of the available resources, input- and output-parameters (= Variables) as well as providers can be quickly gathered directly from the code itself.

Any change to the code will then also be reflected in the output of the documentation every time this information is gathered.

All that is needed to get started is to fetch the binary from the release page and place it into the $path of the OS.


How it works

The process is simple:

  1. Scan the files for variables, providers and resources.
  2. Structure and format the data.
  3. Output the data in various formats.

Let’s assume we have a more or less simple structure of a Terraform workspace. It does not really matter if it is a workspace or a module; the principle is the same.

/
- main.tf
- variables.tf
- outputs.tf

For this example the files only contain a bare minimum of code.

# main.tf
resource "null_resource" "terraform-docs" {
}
# variables.tf
variable "foo" {
  default     = "bar"
  description = "Example variable"
  type        = string
}
# outputs.tf
output "example" {
  value       = var.foo
  description = "Some example output."
}

Notice that the variable definition has a description tag, type parameter and default tag. Those can be optional, but in order to create good documentation, any additional information is useful.

Let’s run terraform-docs with just this basic structure and output markdown formatting:

$ terraform-docs markdown .
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| null | n/a |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| foo | Example variable | `string` | `"bar"` | no |

## Outputs

| Name | Description |
|------|-------------|
| example | Some example output. |

This is valid markdown syntax in the output right there. Alternatively other formats are supports like asciidoc, json, toml, xml, yaml and others.

All variables, outputs and providers have been gathered from the code. At this point you can also see why adding a description is actually quite useful.

If we redirect that into into a markdown file, we are half way with creating the README file.

terraform-docs markdown . | tee README.md

But what about requirements? This is a usual part of a technical Readme file.

Well, if we add another file like e.g.versions.tf with the following example code and run terraform-docs again, our documentation will be updated as well:

/
main.tf
variables.tf
outputs.tf
README.md
versions.tf
# versions.tf
terraform {
    required_version =  "0.13.0"
}
$ terraform-docs markdown . | tee README.md
## Requirements

| Name | Version |
|------|---------|
| terraform | 0.13.0 |

## Providers

| Name | Version |
|------|---------|
| null | n/a |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| foo | Example variable | `string` | `"bar"` | no |

## Outputs

| Name | Description |
|------|-------------|
| example | Some example output. |

The custom parts

If we now could add some custom documentation as well, we would have all base covert.

For achieving this we can either provide a separate file for including into the output or extend the header area in the terraform main.tf file.

The latter option has the additional benefit that there will one file less in the repository cluttering around.

This requires to put the additional information in place. As long as we choose to create a markdown formatted Readme file, the information should also be valid markdown. Other output formats (like asciidoc) require other formatting headers.

/**
  * # README.md
  *
  * This is the readme of the example repository for terraform-docs poc.
  */
resource "null_resource" "terraform-docs" {

}

Imagine a bit more and detailed information in a real-life scenario than provided in this example.

Running terraform-docs now will add the header of main.tf at the beginning of the Readme output:

$ terraform-docs markdown . | tee README.md
# README.md

This is the readme of the example repository for terraform-docs poc.

## Requirements

| Name | Version |
|------|---------|
| terraform | 0.13.0 |

## Providers

| Name | Version |
|------|---------|
| null | n/a |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| foo | Example variable | `string` | `"bar"` | no |

## Outputs

| Name | Description |
|------|-------------|
| example | Some example output. |

Automation, documentation, integration

There are multiple ways how this can be integrated into existing workflows:

  • Local git-hooks could update the Readme.md, add it to the stashing area and commit the changes,
  • CI/CD pipelines as in GitLab or on GitHub can do this task and update the documentation as a part of the code deployment.

In a simple project like in this example containing terraform-code it can be a sufficient way of creating a baseline documentation and ensure it is being kept update-to-date automatically and completely.

Additionally, there’s a range of configuration settings that let you further take influence on the output and formatting.

Those let you for example:

  • select sections to be hidden
  • alter the table layout
  • suppress sensitive information

All those settings can then safely be stored in a configuration file terraform-docs.yml in the working directory. That will keep the CLI command short and neat. The example from above can be shortened a bit by this:

---
# .terraform-docs.yml
formatter: markdown
header-from: main.tf
sort:
  enabled: true
$ terraform-docs .
...

The use of automation for documentation clearly has its benefits.

  • The shortened update time from code changes to documentation reflecting those changes,
  • Elimination of translation errors from code to documentation,
  • Reducing the amount of documentation requirements,
  • Increasing the standard of documentation to a guaranteed base level,
  • Reducing the amount of work to create documentation.

This example was focused only on Terraform code and documentation. These concepts and its benefits can be transferred over to other projects and code bases. Indeed, many projects are already using automatically created documentation in their output.

The techniques may vary though.

Daniel Buøy-Vehn

Senior Systems Consultant at Redpill Linpro

Daniel works with automation in the realm of Ansible, AWX, Tower, Terraform and Puppet. He rolls out mainly to our customer in Norway to assist them with the integration and automation projects.

Just-Make-toolbox

make is a utility for automating builds. You specify the source and the build file and make will determine which file(s) have to be re-built. Using this functionality in make as an all-round tool for command running as well, is considered common practice. Yes, you could write Shell scripts for this instead and they would be probably equally good. But using make has its own charm (and gets you karma points).

Even this ... [continue reading]

Containerized Development Environment

Published on February 28, 2024

Ansible-runner

Published on February 27, 2024