zclconf / go-cty

A type system for dynamic values in Go applications
MIT License
338 stars 70 forks source link

cty.StringVal always doubles $ in ${} output Pt2 #163

Closed tcgbrett closed 9 months ago

tcgbrett commented 1 year ago

Hello again. With all due respect, I would like to open this question one more time. I am doing so because I believe my reasoning wasn't clear last time. Regardless of what HCL expects, CTY will not let me just print a single $. Therefore, I believe I am facing an edge case in CTY itself, not HCL. I tried to search this repo for where that may be happening, but I am not proficient enough to solve it. I looked for $ and none of the references seemed to match what I was looking for.

Hey @apparentlymart,

Thank you for the response! Hmm, my goal was to actually not escape it, as I don't want the resulting string to be interpreted literally. I am using CTY to generate HCL for me so I don't have to write dozens of HCL files by hand. My goal is that the CTY will spit out some HCL that has template interpolation baked into it.

To give the exact example, I'm building a bunch of New Relic dashboards. I want CTY to be able to produce HCL interpolation. Here's what I keep getting stuck with tho:

nrql {
  query = "FROM Metric SELECT clamp_max(sum(newrelic.sli.good) / sum(newrelic.sli.valid) * 100, 100) as 'SLO compliance' WHERE entity.guid = $${newrelic_service_level.asdf---test---order-management_apm.id}"
}

And here is the go code

baseSLOQuery := fmt.Sprintf("FROM Metric SELECT clamp_max(sum(newrelic.sli.good) / sum(newrelic.sli.valid) * 100, 100) as 'SLO compliance' WHERE entity.guid = ${newrelic_service_level.%s.id}", neatSLOName)
nrqlBody.SetAttributeValue("query", cty.StringVal(baseSLOQuery))

Do you have any suggestions? Thanks again for reading and commenting :)

Originally posted by @tcgbrett in https://github.com/zclconf/go-cty/issues/161#issuecomment-1549719974

apparentlymart commented 9 months ago

Hi @tcgbrett,

Sorry I missed this before.

The escaping you are concerned about is being done by nrqlBody.SetAttributeValue (in HCL), not by cty.StringVal.

Here's the SetAttributeValue function's implementation: https://github.com/hashicorp/hcl/blob/63067e819cb6e8044d34b0a32d198a4941f34e42/hclwrite/ast_body.go#L168-L179

It calls NewExpressionLiteral: https://github.com/hashicorp/hcl/blob/63067e819cb6e8044d34b0a32d198a4941f34e42/hclwrite/ast_expression.go#L63-L68

...which calls TokensForValue which then, after a few other calls I'm skipping over, escapes the given string: https://github.com/hashicorp/hcl/blob/63067e819cb6e8044d34b0a32d198a4941f34e42/hclwrite/generate.go#L220

That escapeQuotedStringLit function is the one doing the escaping: https://github.com/hashicorp/hcl/blob/63067e819cb6e8044d34b0a32d198a4941f34e42/hclwrite/generate.go#L367-L373

It does that because SetAttributeValue is for setting the attribute to a constant value, not for setting it to an arbitrary expression. hclwrite currently lacks a function for setting an attribute to be a string template, because it lacks any model of string templates beyond the minimum required to generate literal strings. The closest you can get with the current API is to use SetAttributeRaw, but that requires you to hand-write the individual tokens that the template expression is built from.

hclwrite's model of expressions was originally built around what Terraform needed for some of its work and so it's lacking various features for constructing arbitrary expressions. It should be possible in principle to add an API for building string template expressions, and other kinds of expressions, but that's a topic for HashiCorp's HCL repository, rather than this codebase.

I also can't promise that the Terraform team at HashiCorp (who are the primarily maintainers of HCL) will be able to prioritize this, both because in this repository I'm wearing my cty maintainer hat (this is a personal project that HashiCorp projects depend on, rather than a HashiCorp project) and because even when I'm wearing my Terraform team hat I'm not the one who decides the team's development priorities. Opening an issue in the HCL repository to discuss your use-case would probably be the best next step, though.