Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve references (eval) #159

Closed
denfren opened this issue Jan 25, 2023 · 4 comments
Closed

Resolve references (eval) #159

denfren opened this issue Jan 25, 2023 · 4 comments

Comments

@denfren
Copy link
Contributor

denfren commented Jan 25, 2023

I'm looking into having references like in terraform.

resource aws_something my_something {
  name = "something"
}

output foo {
  value = resource.aws_something.my_something
}

Is this something you already thought about and can share your thoughts? Is this something that you think is in scope of this library or should be external (extra crate inside or outside of this repo)?

@denfren denfren changed the title Resolven Resolve references (eval) Jan 25, 2023
@martinohmann
Copy link
Owner

martinohmann commented Jan 26, 2023

Hi,

you can already implement something like this yourself, by using parts of the body as evaluation context, here's a quick (not very elegant, though) example:

use hcl::eval::{Context, Evaluate};
use indoc::indoc;
use serde_json::value::Value;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = indoc! {r#"
        resource "aws_s3_bucket" "bucket" {
          bucket = "my-bucket"
        }

        resource "aws_s3_bucket_acl" "bucket_acl" {
          bucket = aws_s3_bucket.bucket.bucket
          acl    = "private"
        }
    "#};

    let body = hcl::parse(input)?;

    let value: Value = hcl::from_body(body.clone())?;

    let mut ctx = Context::new();

    for (key, value) in value["resource"].as_object().unwrap() {
        ctx.declare_var(key.clone(), hcl::to_value(value)?);
    }

    let evaluated = body.evaluate(&ctx)?;

    let formatted = hcl::format::to_string(&evaluated)?;

    let expected = indoc! {r#"
        resource "aws_s3_bucket" "bucket" {
          bucket = "my-bucket"
        }

        resource "aws_s3_bucket_acl" "bucket_acl" {
          bucket = "my-bucket"
          acl = "private"
        }
    "#};

    assert_eq!(formatted, expected);

    Ok(())
}

However, this approach only works if the thing that is referenced already has a known value (that is: it's not an expression that itself needs to be resolved first). For unknown values you would need to do multiple resolution passes.

I'm not sure if this is something that should be part of this crate, since the self-referential natural of terraform and the treatment of dynamic values is something rather special and not part of the original HCL spec. So maybe this would make sense in separate crate that extends the HCL functionality.

Terraform also has computed attributes (e.g. aws_s3_bucket.bucket.id is computed), and i'm not sure how to model these. It probably requires a configuration schema that's specific to terraform.

Also have a look at #150 which provides examples for a similar issue.

@denfren
Copy link
Contributor Author

denfren commented Jan 26, 2023

First of all, thank you for getting back with an example - pretty cool!

I have done that approach where I made a tree of dependencies (dive into all structures to find the Expression::Variable and Expression::Traversal leaf nodes) and then figure out in what order I have to resolve them - but as you said: self-referential blocks are a problem (because I was resolving full blocks, later tried to dynamically split up evaluation into finer chunks...but that got messy)

For my next attempt I'm thinking about extending eval::Context or replace it with a trait for additional flexibility (lazy eval)

@martinohmann
Copy link
Owner

This gave me an idea. It might be possible to support partial evaluation via an additional evaluate_partial method on the Evaluate trait which would just skip evaluating expressions that contain variable references that are not known yet, instead of failing the evaluation. That way you could evaluate an HCL document in multiple passes, by adding variable values to the context that were resolved in a previous pass.

I'll try to explore if this idea would work out as I imagine when I have some time.

@martinohmann
Copy link
Owner

Closing in favor of #184

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants