hashicorp / terraform-cdk

Define infrastructure resources using programming constructs and provision them using HashiCorp Terraform
https://www.terraform.io/cdktf
Mozilla Public License 2.0
4.88k stars 455 forks source link

CLI: HCL synth fails on null value in module configuration #3697

Open Hi-Fi opened 3 months ago

Hi-Fi commented 3 months ago

Expected Behavior

Stack is synthesized corretly as HCL

Actual Behavior

Crash with error:

npm run cdktf synth -- --hcl

> test-repo@1.0.0 cdktf
> cdktf synth --hcl

⠹  Synthesizing
[2024-08-09T10:57:17.900] [ERROR] default - /Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:505
    ...Object.entries(jsonObject).map(([name, value]) => {
              ^

[2024-08-09T10:57:17.901] [ERROR] default - TypeError: Cannot convert undefined or null to object
    at Function.entries (<anonymous>)
    at renderFuzzyJsonObject (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:505:15)
    at renderFuzzyJsonExpression (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:525:12)
    at /Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:670:29
    at Array.map (<anonymous>)
    at renderAttributes (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:586:6)
    at renderModule (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:372:3)
    at /Users/user/test-repo/node_modules/cdktf/lib/terraform-stack.ts:292:49
    at Array.map (<anonymous>)
ERROR: cdktf encountered an error while synthesizing

Synth command: npx ts-node bin/main.ts
Error:         non-zero exit code 1

Command output on stderr:

    /Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:505
        ...Object.entries(jsonObject).map(([name, value]) => {
                  ^
    TypeError: Cannot convert undefined or null to object
        at Function.entries (<anonymous>)
        at renderFuzzyJsonObject (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:505:15)
        at renderFuzzyJsonExpression (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:525:12)
        at /Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:670:29
        at Array.map (<anonymous>)
        at renderAttributes (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:586:6)
        at renderModule (/Users/user/test-repo/node_modules/cdktf/lib/hcl/render.ts:372:3)
        at /Users/user/test-repo/node_modules/cdktf/lib/terraform-stack.ts:292:49
        at Array.map (<anonymous>)
        at RunnerAcrStack.toHclTerraform (/Users/user/test-repo/node_modules/cdktf/lib/terraform-stack.ts:268:8)

⠧  Synthesizing

Steps to Reproduce

  1. Create stack with resource that has ignore_changes set to single item
  2. Try to synthesize stack to HCL (cdktf synth --hcl)

Versions

language: typescript cdktf-cli: 0.20.8 node: v20.13.1 cdktf: 0.20.8 constructs: 10.3.0 jsii: null terraform: 1.9.4 arch: arm64 os: darwin 23.6.0

Providers

None

Gist

No response

Possible Solutions

Check handling of data in HCL render function.

With some console logging in renderFuzzyJsonExpression (like:

function renderFuzzyJsonExpression(jsonExpression) {
    console.log(jsonExpression);
    console.log(typeof jsonExpression)

output was:

    [ 'tags' ]
    object
    tags
    string
    null
    object

Workarounds

Don't synth HCL

Anything Else?

No response

References

No response

Help Wanted

Community Note

Hi-Fi commented 3 months ago

Apparently (unit tests) issue is not at the ignore_changes block, so have to check things a bit more. Also simple example stacks doesn't cause issues.

It seems that error is coming from AVM module https://github.com/Azure/terraform-azurerm-avm-res-containerregistry-registry/tree/0.1.0.

With some logging output is:

    rendering module: AvmContainerRegistry
    Handling attribute: location
    Handling attribute: managed_identities
    null
    object

renderAttributes function get following attributes:

{
  location: {
    value: '${azurerm_resource_group.ResourceGroup.location}',
    type: 'any'
  },
  managed_identities: { value: null, type: 'any' },
  name: { value: 'testacr', type: 'any' },
  network_rule_bypass_option: { value: 'AzureServices', type: 'any' },
  public_network_access_enabled: { value: false, type: 'any' },
  resource_group_name: {
    value: '${azurerm_resource_group.ResourceGroup.name}',
    type: 'any'
  },
  source: {
    value: 'example.artifactory.com/terraform-modules__Azure/avm-res-containerregistry-registry/azurerm',
    type: 'simple',
    isBlock: false,
    storageClassType: 'string'
  },
  version: {
    value: '0.1.0',
    type: 'simple',
    isBlock: false,
    storageClassType: 'string'
  },
  '//': {
    metadata: {
      path: 'test-avm-stack',
      uniqueId: 'AvmContainerRegistry'
    }
  }
}

Same applies also to module 0.2.0 version.

In CDKTF code identities are explicitly set to null, as otherwise module would add identity to ACR which is not wanted.

Hi-Fi commented 3 months ago

Code that cause the issue also at Azure/terraform-azurerm-avm-res-containerregistry-registry#76

mutahhir commented 3 months ago

This needs a bit more clarity. I guess the issue is that in certain conditions, passing a null value to attribute causes the error. Can you give me a minimal reproducible code snippet to chase this down?

Hi-Fi commented 3 months ago

Init empty project with typescript with CDKTF

cdktf.json:

{
  "language": "typescript",
  "app": "npx ts-node main.ts",
  "projectId": "a24002df-12f0-46e1-9d08-9cfcb038742f",
  "sendCrashReports": "false",
  "terraformProviders": [],
  "terraformModules": [
    "Azure/avm-res-storage-storageaccount/azurerm@0.2.2"
  ],
  "context": {

  }
}

Main.ts:

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AvmResStorageStorageaccount } from "./.gen/modules/Azure/azurerm/avm-res-storage-storageaccount"

class MyStack extends TerraformStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    new AvmResStorageStorageaccount(this, "storage", {
      location: "eastus",
      resourceGroupName: "cdktf-test",
      name: "cdktfstorage",
      managedIdentities: null
    });
  }
}

const app = new App();
new MyStack(app, "cdktf-test");
app.synth();