Search code examples
open-policy-agent

How to pass input to query rule defining a set document


In the documentation of OPA there are a lot of examples for generating sets/arrays/objects for querying, e.g.:

app_to_hostnames[app_name] = hostnames {
    app := apps[_]
    app_name := app.name
    hostnames := [hostname | name := app.servers[_]
                            s := sites[_].servers[_]
                            s.name == name
                            hostname := s.hostname]
}

However, in the documentation, all data is statically defined: in the example the variables apps already exists and is defined as some json object.

For reusability I would like to define a function that returns a set/array/object but allows for the input to be passed dynamically. In essence what I want to try to do is the following:

app_to_hostnames[app_name](apps) = hostnames {
    app := apps[_]
    app_name := app.name
    hostnames := [hostname | name := app.servers[_]
                            s := sites[_].servers[_]
                            s.name == name
                            hostname := s.hostname]
}

Where in this case apps is passed as a function input. Is there a way to achieve this in Rego policies? Or should I approach the problem in a different way? Or is there a way to pass different inputs to different policies?

I know you could send the input via the REST API to a specific policy and control it that way, but in this case I am using conftest which passes the input document (for example a json file) to a compiled set of rego policies) via the terminal so using the REST API won't work for me.


Solution

  • The input and data documents are global. You can always replace their values on a given expression using the with keyword but sometimes it's more natural to wrap the logic in a function.

    That specific example can be rewritten using a comprehension:

    app_to_hostnames(apps, sites) = {app_name: hostnames |
        app := apps[_]
        app_name := app.name
        hostnames := [hostname | name := app.servers[_]
                                s := sites[_].servers[_]
                                s.name == name
                                hostname := s.hostname]
    }
    

    Alternatively you can use the with keyword to temporarily replace portions of the data or input documents. For example, if apps, sites were imported from under data in the file containing app_to_hostnames you could query app_to_hostnames as follows:

    mock_apps := [
      {"name": "foo", "servers": ["s1"]},
      {"name": "bar", "servers": ["s2", "s3"]},
    ]
    
    mock_sites := [
      {"servers": [{"name": "s1", "hostname": "x.com"}]},
      {"servers": [{"name": "s2", "hostname": "y.com"}, {"name": "s3", "hostname": "z.com"}]},
    ]
    
    app_to_hostnames == {"foo": ["x.com"], "bar": ["y.com", "z.com"]} with data.apps as mock_apps with data.sites as mock_sites
    

    Here's a link to the policy inside the playground: https://play.openpolicyagent.org/p/pEp6BLCtgn