terraformyamlterraform-provider-awsterraform-provider-gcp

Terraform yamldecode Fails When YAML Structure Changes


I have a YAML file structured as follows:

 alerts:
   la:
     la1:
       description: "la1"
       severity: 1
       resource_counter: "004"
       enabled: true
   ma:
     ma1:
       description: "ma1"
       severity: 2
       enabled: true
       resource_counter: "002"
     ma2:
       description: "ma2"
       severity: 2
       enabled: true
       resource_counter: "003"

This YAML file is being parsed correctly in Terraform using the following locals block:

 locals {
    all_alerts     = try(coalesce(yamldecode(file("${path.root}/alerts.yaml"))["alerts"], {}), {})
   metric_alerts  = try(local.all_alerts["ma"], {})
   log_alerts     = try(local.all_alerts["la"], {})
  }

However, when I update the YAML file by adding a new key (scopes) under the alerts block like this:

 alerts:
   la:
     la1:
       description: "la1"
       severity: 1
       resource_counter: "004"
       enabled: true
   ma:
     ma1:
       description: "ma1"
       severity: 2
       enabled: true
       resource_counter: "002"
     ma2:
       description: "ma2"
       severity: 2
       enabled: true
       resource_counter: "003"
       scopes:
           - "test"

Terraform stops producing any output from the locals. All the decoded values (all_alerts, metric_alerts, and log_alerts) are now empty:

+ all_alerts_debug = {}
+ log_alerts_debug = {}
+ metric_alerts_debug = {}

It seems that the addition of the scopes key under alerts is causing the structure to break or be interpreted differently by Terraform

Also , when i remove the 'la' section then also it will work

 alerts:
   ma:
     ma1:
       description: "ma1"
       severity: 2
       enabled: true
       resource_counter: "002"
     ma2:
       description: "ma2"
       severity: 2
       enabled: true
       resource_counter: "003"
       scopes:
           - "test"

Same behaviour when executed against local terrafrom as well as terrafrom workspace


Solution

  • I managed to reproduce it with OpenTofu 1.9.0. The reason that this didn't work was because the second argument in the coalesce function is a map. The type of output produced by yamldecode is object (of objects):

    > type(local.all_alerts)
    object({
        la: object({
            la1: object({
                description: string,
                enabled: bool,
                resource_counter: string,
                severity: number,
            }),
        }),
        ma: object({
            ma1: object({
                description: string,
                enabled: bool,
                resource_counter: string,
                severity: number,
            }),
            ma2: object({
                description: string,
                enabled: bool,
                resource_counter: string,
                scopes: tuple([
                    string,
                ]),
                severity: number,
            }),
        }),
    })
    

    Using only coalesce(yamldecode(file("${path.root}/test.yml"))\["alerts"\], {}) (i.e, without try) throws the following error:

    Error: Error in function call

    on locals.tf line 164, in locals: 164: all_alerts = coalesce(yamldecode(file("${path.root}/test.yml"))["alerts"], {})

    while calling coalesce(vals...)

    path.root is "."

    Call to function "coalesce" failed: all arguments must have the same type.

    Since the call to the coalesce function fails, the try block then outputs the empty map ({}). Using nested built-in functions kind of obfuscates the real error in this case, so I would probably stick to using only yamldecode and then implement try logic elsewhere if required.