Search code examples
terraformnewrelicterraform0.12+

Terraform iterate over nested data


I'm trying to create New Relic's service-level objects based on a yaml config file that provides the relevant configuration. My yaml configuration:

slo:
  targets:
  - first_slo:
    name: "My First SLO"
    endpoints:
      - path: /api/method1
        method: GET
      - path: /api/method2
        method: PUT
    objectives:
      availability:
        threshold: 99.9
  - first_slo:
    name: "My Second SLO"
    endpoints:
      - path: /api/method12
        method: GET
      - path: /api/method23
        method: PUT
    objectives:
      availability:
        threshold: 99.99

I want to iterate over this example configuration to build the object, but I'm struggling to form the right NRQL query using a nested iteration.

My terraform file:

resource "newrelic_service_level" "availability" {
  for_each  = var.config.slo.targets
  guid = var.guid
  name = "${each.value.name} - Availability"
  description = "Proportion of requests that are served successfully."

  events {
    account_id = var.account_id
    valid_events {
      from = "Transaction"
      where = "transactionType='Web' AND entityGuid = '${var.guid}' AND (OR_CONDITION_BETWEEN_ALL_THE_METHODS_AND_URIS)"
    }
    bad_events {
      from = "Transaction"
      where = "transactionType= 'Web' AND entityGuid = '${var.guid}' AND numeric(response.status) >= 500 AND (OR_CONDITION_BETWEEN_ALL_THE_METHODS_AND_URIS)"
    }
  }

  objective {
    target = each.value.objectives.availability.threshold
    time_window {
      rolling {
        count = 7
        unit = "DAY"
      }
    }
  }
}

So basically what I'm trying to do here, is create a service level with an NRQL query that filters only for the specific combination of URI and method that are relevant for this specific target - the URI and methods that I have in my config file.

So for the first SLO, OR_CONDITION_BETWEEN_ALL_THE_METHODS_AND_URIS should translate to something like this: (request.uri = '/api/method1' AND request.method = 'GET') OR (request.uri = '/api/method2' AND request.method = 'PUT')

My current solution would be to build the query manually and add it to the configurations for each SLO, but it is not readable and hard to maintain.

I would highly appreciate any suggestions on how to build the query dynamically.


Solution

  • You can certainly build that query with Terraform. Here's a wee .tf file that shows how you could do it:

    locals {
      config = yamldecode(file("${path.root}/vars.yaml"))
      parsed = [for d in local.config.slo.targets : {
        name : d["name"]
        condition : join(" OR ", [for e in d["endpoints"] : "(request.uri = '${e["path"]}' AND request.method = '${e["method"]}')"])
      }]
    }
    
    output "parsed" {
      value = local.parsed
    }
    
    

    This expects your yaml file to be sitting next to it with name vars.yaml, and produces:

    $ terraform plan
    
    Changes to Outputs:
      + parsed = [
          + {
              + condition = "(request.uri = '/api/method1' AND request.method = 'GET') OR (request.uri = '/api/method2' AND request.method = 'PUT')"
              + name      = "My First SLO"
            },
          + {
              + condition = "(request.uri = '/api/method12' AND request.method = 'GET') OR (request.uri = '/api/method23' AND request.method = 'PUT')"
              + name      = "My Second SLO"
            },
        ]
    

    For your module, you can just use the join(...) part in place of OR_CONDITION_BETWEEN_ALL_THE_METHODS_AND_URIS. Having it repeated should be fine (as long as you document it, naturally), but if you don't like the big long line, you can create a sub-module to encapsulate it. Or you could build the query string in a pre-processing locals block, possibly using the merge function to just add the query string alongside the rest of each target's config.