SEANK.H.LIAO

unified config syntax

one config language to rule them all?

unified config

If you've written any amount of deployment config for a moderately complex app, you might be familiar with the pattern of: the deployment config being structured, the app config being structured, but embedded as a string within the deployment config. This is unfortunate, since you lose syntax checking etc.

Example, yaml in yaml

 1apiVersion: v1
 2kind: ConfigMap
 3metadata:
 4  name: map1
 5data:
 6  config.yaml: |
 7    foo: bar
 8    structured:
 9      config:
10        - in
11        - a
12        - string    

Example: json in hcl

 1resource "aws_iam_policy" "policy" {
 2  name = "foo"
 3  policy = <<EOF
 4    {
 5      "Version": "1"
 6      "Statement": [{
 7        "Action": [
 8          "ec2:Describe*"
 9        ]
10      }]
11    }
12  EOF
13}

breakout into a file

One solution would be to break things out into their own files, and include the result, each one gets its own filetype, but that doesn't compose well, and if you need templating within the nested config, you now have an incomplete config with invalid syntax. Also, this is very much tied to the tool you're using

Example: helm

1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: map1
5data:
6  config.yaml: {{ $.Files.Get "config.yaml" }}

Example: terraform

1resource "aws_iam_policy" "policy" {
2  name = "foo"
3  policy = file("policy.json")
4}

config conversion

So, if your config language supports it, you can do the conversions inline:

 1resource "aws_iam_policy" "policy" {
 2  name = "foo"
 3  policy = jsonencode({
 4    Version = "1"
 5    Statement = [{
 6      Action = [
 7        "ec2:Describe*",
 8      ]
 9    }]
10  })
11}

cue

So what do you do? Thankfully, most config tools accept json as input, so instead of writing the You add a level of indirection with cue.

1resource: aws_iam_policy: policy: {
2  name: "foo"
3  policy: json.Marshal({
4    Version: "1"
5    Action: [
6      "ec2:Describe*",
7    ]
8  })
9}
 1apiVersion: v1
 2kind: ConfigMap
 3metadata: name: foo
 4data: config.yaml: yaml.Marshal({
 5  foo: bar
 6  structured: config: [
 7    "in",
 8    "a",
 9    "string",
10  ]
11})