Terraform and YAML related notes
—
If you manage applications, you likely come across a ton of structured data that stays mostly static but must be tracked, version-controlled and be visible, especially when someone modifies it. For some folks, that is a CMS, for others a stack of papyri and for some more it is just a long oral tradition of chanting or yelling. I, however, prefer readable, structured text files. That's why I like the idea of Terraform or infrastructure-as-code.
Last year, we faced a problem for management of some of our infrastructure-data. We wanted to manage the Consul KV store data via Terraform and not depend on Consul's UI. The problem was that there was no default Terraform provider for the particular task of manipulation of Consul KV store. There are provider resources like "consul_keys" and "consul_key_prefix" but they alone were not adequate for a cleaner solution. We have multiple datacenters and it is tiresome to edit keys and values when needed. The data had a ton of repetition across various datacenters with some common parts. Moreover, Web UI does not keep a record of edits. As a result, the visibility within the team was close to zero.
Given the lack of any such plugin, at that time, we went on a path to use Terraform's external data source and decided to store the KV data in a YAML file which will be read by the external data source and will populate the "consul_key_prefix". First, external data source is your custom program that reads from stdin and spits out on stdout, both as JSON objects. The official documentation for this "external data source" provider shows that you define it as
data "external" "example" {
# Call "python /path/to/mod/example.py"
program = [
"python",
"${path.module}/example.py"
]
# with JSON query
query = {
id = "abc123"
}
}
In the problem that I stated above, it was decided to store the Consul Key/Value pairs as nested YAML file for each datacenter and a common section for common keys. Something like:
common:
key_common_1: value_common_1
dc1:
<<: *common
key11: value11
key12: value12
key13:
key131: value131
dc2:
<<: *common
key21: value21
key22: value22
key23:
key231: value231
The wonderful part of YAML is that it is machine-and-human-readable, and can succinctly represent hierarchy in a document. Hence, it is well-suited for the representation of various datacenters and their respective KV data, which is what we wanted to do. Take the key "key131" in "Datacenter 1". You can depict the "path" to "key131" as Consul KV via dc1/key13/key131
Now, comes the question of what to do with external datasource. You can write that in any language/platform that can read from stdin and write to stdout. In the official documentation they use bash. However, I chose Python for its simplicity, good YAML support and JSON interoperability. Also, I could get help from some team members if I ever got stuck. With these tools, the last thing I want to add is that you can use Python's flatdict with delimiter="/" as
import flatdict
with open(file_path, 'r') as yf:
yaml_dict = flatdict.FlatDict(yaml.load(yf),
delimiter='/')
This creates a flattened dictionary, each key of which is the "path" with segments delimited by "/". Example dc1/key13/key131
All that is left now is manipulation of the above dict if needed and writing JSON to stdout so it can be read by the Terraform resource "consul_key_prefix". 😇
resource "consul_key_prefix" "keys" {
datacenter = "${var.datacenter}"
path_prefix = "${var.prefix}${var.path}"
subkeys = "${data.external.mydata.result}"
}
But, I am not done telling you about this yet. My friend and colleague Borys Pierov wrote new set of Terraform provider plugins because there was a need for a good Consul ACL management provider. He abstracted a bunch of stuff into independent plugins so you can go from flexible to powerful, if you want.
- terraform-provider-transform: Terraform data sources providing data transformations missing from core Terraform
- terraform-provider-stateful: Generic abstract stateful resources to manage arbitrary objects by executing arbitrary commands
- terraform-provider-yaml: Terraform data source that can consume YAML input
- terraform-provider-consulacl: Consul ACL Terraform Provider
So, if you use Consul and you need to manage KV or ACL, you do not have any excuses to not use the above tools. Please let me know if I missed something or there are alternate approaches that you've taken. Thanks for reading. 😊