Rego Rules with Duplicate Names

Rego is the language used by the Open Policy Agent. I use it in Conftest for infrastructure testing. Together, they’re great for writing policies that enforce requirements in things like Terraform HCL and Kubernetes YAML. Check out the conftest usage guide for an example.

In Rego, you write rules that check if data structures look the way you expected. Rules are kind of like functions in Python. They contain the logic you write.

Two rules can have the same name. The way Rego handles that surprised me. I wrote some bugs before I got used to it.

Alan Tunnicliffe/Shutterstock.com

In Python, if two functions have the same name:

def f1():
    return 'first'

def f1():
    return 'second'

print(f1())

The second function gets executed:

python ./test.py
second

In Rego, the value of a rule is a set containing the values of all the rules that have the same name.

If we use the Rego Playground to evaluate this rule:

even_or_odd[value] {
	contains(input.message, "one")
    value := "odd"
}

With this input:

{
    "message": "one two three"
}

We get a set with one value:

 {
    "even_or_odd": [
        "odd"
    ]
}

We can confirm it’s a set with the built-in is_set function in a new rule:

even_or_odd[value] {
	contains(input.message, "one")
    value := "odd"
}

even_or_odd_is_set[value] {
	value := is_set(even_or_odd)
}

This evaluates to:

{
    "even_or_odd": [
        "odd"
    ],
    "even_or_odd_is_set": [
        true
    ]
}

If we add another rule that’s also named even_or_odd and that has a new value:

even_or_odd[value] {
	contains(input.message, "one")
    value := "odd"
}

even_or_odd[value] {
	contains(input.message, "two")
    value := "even"
}

We get the new value in the even_or_odd set:

{
    "even_or_odd": [
        "even",
        "odd"
    ]
}

Sets contain unique values, so if we add a new rule with an old value:

even_or_odd[value] {
	contains(input.message, "one")
    value := "odd"
}

even_or_odd[value] {
	contains(input.message, "two")
    value := "even"
}

even_or_odd[value] {
	contains(input.message, "three")
    value := "odd" # This value already exists in the set.
}

We get the same two values (not a third):

{
    "even_or_odd": [
        "even",
        "odd"
    ]
}

These are small details, but they weren’t obvious to me when I started. Hopefully they save you some troubleshooting.

You might also want to check out these articles: