grafana / terraform-provider-grafana

Terraform Grafana provider
https://www.terraform.io/docs/providers/grafana/
Mozilla Public License 2.0
422 stars 226 forks source link

Use Terraform arguments/blocks instead of raw JSON to configure dashboards #299

Open bnjns opened 2 years ago

bnjns commented 2 years ago

I appreciate that this is a potentially controversial thing to raise, likely to spark debate, and will be a fair chunk of work but I'm starting to get our Grafana configuration into version control using the terraform provider, and as someone who uses Terraform a lot, the current way of configuring dashboards is proving to be somewhat painful, and difficult for those less familiar with the JSON syntax to review.

While the use of config_json to simply provide the raw JSON makes it easy to copy/paste from an existing dashboard, it doesn't really follow Terraform's concept of being a declarative configuration language. I think it would be much better if dashboards were configured with arguments and blocks, as this would allow you to take advantage of the full power of Terraform (using things like dynamic blocks to iterate or enable/disable panels); to be able to achieve this with the current implementation, you have to use template interpolation and/or some hacky jsonencoded variables, which (being invalid JSON) would break any editor that you use to maintain the dashboard config.

I also believe that this approach would allow the provider/API to abstract away the default values, resulting in a much smaller and more maintainable config. It would also make it easier to reference other resources/data sources in the config, as you'd see the reference at the site where it's used.

Here's example of how using blocks and arguments can be an improvement over simple JSON

Using config_json ```hcl resource "grafana_dashboard" "example" { config_json = <
Using blocks and arguments (not a fixed structure, just an example) ```hcl resource "grafana_dashboard" "example" { title = "Example Dashboard" editable = true style = "dark" // Could make use of a default for this time { from = "now-24h" to = "now" } timepicker { refresh_intervals = ["1m", "5m", "15m", "30m", "1h"] } template_variable { // this is empty here, but templating would also be very powerful using blocks } panel { type = "logs" datasource = grafana_data_source.cloudwatch.name title = "Warnings and Errors" grid_position = { h = 11 w = 15 } target { ref_id = "A" query_mode = "Logs" expression = "fields @message\n| filter @message like /level=(warn|error)/\n| sort @timestamp desc" log_group_names = ["/ecs/example-service"] } } panel { type = "graph" datasource = grafana_data_source.cloudwatch.name title = "Log Errors" grid_position = { h = 11 w = 9 x = 15 y = 0 } bars = true fill = 1 fill_gradient = 0 null_point_mode = "null as zero" // This would probably be better as a series of enums - NULL_AS_ZERO yaxis = { align = false align_level = null } target { ref_id = "A" query_mode = "Logs" expression = "filter @message like /level=(error)/\n| stats count (*) as Error by bin(1m)" log_group_names = ["/ecs/example-service"] stats_groups = ["bin(1m)"] } threshold { color_mode = "critical" fill = true line = true op = "gt" value = 0 } // We would probably be able to default to 1 x-axis and 1 y-axis so you'd only need these to override the default config axis { type = "x" mode = "time" } axis { type = "y" format = "short" decimals = 0 min = 0 } legend { show = true } tooltip { shared = true value_type = "individual" } alert { name = "Log Error" for = "5m" frequency = "1m" tags = {} execution_error_state = "alerting" no_data_state = "ok" condition { type = "query" params = ["A", "5m", "now-1m"] operator = { type = "and" } reducer = { type = "avg" } evaluator = { type = "gt" params = [0] } } notification { uid = grafana_alert_notification.example.uid } } } } ```

In terms of implementation, I think this can be done in a fairly generic way that simply takes the HCL syntax and converts it into the equivalent JSON. For the most part, the argument structure would match the JSON, with either blocks or maps for nested objects and blocks for generating lists. Ultimately all this needs to do is generate the JSON, which it then sends to the API as per the current implementation.

While I haven't written a provider myself, I have reasonable experience with Go and Terraform parsing so would be more than happy to directly contribute to this.

inkel commented 2 years ago

Hi @bnjns! Thanks for taking interest in this provider.

First and foremost: this is a personal opinion and doesn't reflect in any way those of Grafana Labs. I love this idea, and I've even thought about it in the past. The benefits you mention as well the painful current experience provides are definitely true. However, having parity with the JSON used by grafana could prove harder to maintain.

One alternative that I'd like to mention for easily maintaining the JSON source and facilitating reviewing changes is using Grafonnet, a Jsonnet library that allows you to declare your dashboards in a simpler way; check the examples for more information.

This doesn't mean that I don't consider this as a good feature to have, although we would need to review this and prioritize it before starting with any work.

Thanks again!

bnjns commented 2 years ago

Yeah, I totally appreciate the concerns you have regarding ensuring this remains aligned with the JSON used by Grafana, and we would definitely need to be careful about how this is implemented - especially when we consider the flexibility and variety of plugins.

I will certainly take a look at Grafonnet, however due to our CI/CD tooling this may be tricky to implement, and on the whole I do feel like "native" support for this in the provider would be the best solution. I may give this a try on a fork to see how feasible implementing this might be, and I'm happy to be involved in whatever way I can (although I respect that ultimately the decision on how the provider works lies with Grafana Labs).

Thanks!

inkel commented 2 years ago

…I do feel like "native" support for this in the provider would be the best solution.

I share the feeling, it would certainly make the whole process more logical; as you mentioned, it would be great to use Terraform attributes for referencing things like datasources.

I'm going to leave this one open as it's an interesting idea.

justinTM commented 2 years ago

We are definitely interested in this as well because templating 150k lines of JSON has gotten cumbersome. Do be aware in Grafana 8.4 the team has been hard at work with CO/CD features and are moving away from JSON and using CUE for the schema. Grafonnet is still the recommended way to template JSON but there will be some big features coming soon.

On Nov 25, 2021, at 9:27 AM, Leandro López @.***> wrote:

 …I do feel like "native" support for this in the provider would be the best solution.

I share the feeling, it would certainly make the whole process more logical; as you mentioned, it would be great to use Terraform attributes for referencing things like datasources.

I'm going to leave this one open as it's an interesting idea.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

inkel commented 2 years ago

Hi folks. I just wanted to give you an update on this one: we just had a planning session and we talked about this idea. The biggest challenge to implement something like this is keeping the resource up to date with the ever evolving dashboard schema, so we are discussing different options.

Sorry I can't give you an ETA for this feature, but rest assured we haven't forgotten about it 😸

justinTM commented 2 years ago

please follow @sdboyer 's work with Thema and polly pkg. Grafana has switched to CUE for dashboard schema, and Thema enables "lenses" which automatically translate between different schema versions. polly will enable Grafana Terraform to better bundle infrastructure with observability, so we can make dashboarding less of a manual, arduous, and unreliable chore.

sdboyer commented 2 years ago

Thanks for offering your time to help with this, @bnjns, and for the informative additions, @justinTM!

We are indeed in the process of moving to a new system for schematizing Grafana objects, including dashboards. First step is schema themselves, and baking them deep into the way we actually build the Grafana software. The intended result there is a clean, reliable foundation for all tools (like terraform) that want to work with Grafana objects from outside Grafana, without the need for time consuming, maintenance heavy, and ultimately unreliable reverse engineering.

The first major prerequisite will be making a standard way of consuming our entity types: grafana/grafana#44808. Once that pattern exists and we've started filling them in, it'll take some time before all the schema are truly maintained and mature - but you'll at least have a coherent place to watch.

Once they're mature "enough", we can contemplate whether it's feasible to make helpers of the sort described in the OP in a similarly tool-agnostic way, or if it makes more sense to have each tool ecosystem make their own concept of helpers/funcs.

bnjns commented 2 years ago

Thanks all for your input - I'm glad this is something that's genuinely being considered!

I'm totally onboard with making sure we take our time to do this properly; we've written some Terraform modules to abstract a lot of the JSON config away so happy for this to wait for the schema changes to become more mature 🙂

sidekick-eimantas commented 1 year ago

Howdy !

Was wondering what the state of this effort was, if it's still in progress? I saw some good work being done on cue models over in the grafana repo.

bnjns commented 1 year ago

I've started a proof of concept here: https://github.com/bnjns/terraform-provider-grafana-dashboard-json

Currently not published, but I plan on releasing it once I've got a couple of panel types available.

julienduchesne commented 1 year ago

This is being actively worked on in https://github.com/grafana/terraform-provider-schemas (still very experimental). The plan is to generate a provider from the new Grafana cue schemas

bengesoff commented 1 month ago

Another idea could be to add a grafonnet() provider-defined function to simplify the process of using grafonnet with Terraform. This would remove the need to install jsonnet and the grafonnet library. Given that there's an official go-jsonnet library, it shouldn't be too tricky to do