So this has taken me 24+ hours to figure out. All docs, references to using OPA
with Terraform are about testing the plan. I want some static analysis on raw
.tf
files. An OPA based linter if you will.
First thing I ran into was iterating over an object, then being able to use
it’s key. In Python I’d do: for k,v in dict
. In Rego, we do hash[key], then
just go and use key
wherever. ¯\_(ツ)_/¯ Maybe this will make more sense
the more I use Rego.
Second thing, I think I was being caught out by was: “Universal Quantification”.
So in my policy where I have not key == computed
, I had used key != computed
which, maybe this will make sense in the future, but right now. Mind blown.
Anyway, the thing you’ve been waiting for. Some examples.
main.tf
module "foo_app_uk_prod" {
source = "./modules/foo"
module = "foo"
project = "app"
region = "uk"
env = "stage"
}
module "foo_database_uk_prod" {
source = "./modules/foo"
module = "foo"
project = "database"
region = "uk"
env = "prod"
}
policy/module.md
package main
deny[msg] {
m := input.module[key]
# Based on the attributes, we compute what the expected module should be named
computed_module_name := sprintf("%s_%s_%s_%s", [m.module, m.project, m.region, m.env])
not key == computed_module_name
msg := sprintf("Module name: %v, does not match computed configuration: %v", [key, computed_module_name])
}
So if we take our main.tf
and module.rego
and run them like so with the handy
tool called conftest
.
$ conftest test main.tf -p policy/module.rego
FAIL - main.tf - main - Module name: foo_app_uk_prod, does not match computed configuration: foo_app_uk_stage
1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions
We can see that we have one failure. This policy/linter is trying to ensure the module name matches the configuration passed to it.
Looks like we’ve messed up when copy and pasting. Our app config, let’s update
line 7 to say prod
and run conftest
again:
$ conftest test main.tf -p policy/module.rego
1 test, 1 passed, 0 warnings, 0 failures, 0 exceptions
Much better.