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

terraform show -json omits data when nested blocks are used and SchemaConfigModeAttr is set #29022

Closed
noelbundick opened this issue Jun 23, 2021 · 7 comments · Fixed by #29522
Labels
bug config confirmed a Terraform Core team member has reproduced this issue

Comments

@noelbundick
Copy link

Terraform Version

Terraform v1.0.0
on linux_amd64

Terraform Configuration Files

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=2.61.0"
    }
  }
}

provider "azurerm" {
  features {
  }
}

resource "azurerm_resource_group" "rg" {
  name     = "test-rg99"
  location = "westus2"
}

resource "azurerm_network_security_group" "nsg1" {
  name                = "nsg1"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    access                     = "allow"
    description                = "value"
    destination_address_prefix = "*"
    destination_port_range     = "80"
    direction                  = "inbound"
    name                       = "allow http"
    priority                   = 100
    protocol                   = "tcp"
    source_address_prefix      = "*"
    source_port_range          = "*"
  }
}

resource "azurerm_virtual_network" "vnet1" {
  name                = "vnet1"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]

  subnet {
    address_prefix = "10.0.0.0/24"
    name           = "subnet1"
    security_group = azurerm_network_security_group.nsg1.id
  }

}

Debug Output

https://gist.github.com/noelbundick/9cdb1d7960b6970f2c861fd7e16f1626

Crash Output

No crash

Expected Behavior

The JSON output for the plan should include the references from vnet1.subnet1 -> nsg1

Actual Behavior

The schema for azurerm_virtual_network explicitly defines subnet as SchemaConfigModeAttr, so it gets dropped when marshaling to JSON. There is no way to see the link from subnet to NSG in the generated JSON for the plan.

Relevant code paths:

Steps to Reproduce

  1. terraform init
  2. terraform plan -out=azurerm.tfplan
  3. terraform show -json azurerm.tfplan

Additional Context

azurerm_virtual_network supports using subnet both as a nested block as well as an attribute (setting to [] to delete subnets): https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network#subnet

Changing SchemaConfigModeAttr to SchemaConfigModeAuto resolves the issue with JSON exports, but breaks the ability to delete subnets. @tombuildsstuff indicated this is a bug in Terraform Core vs. being azurerm provider specific.

References

cc: @rguthriemsft @Joshua-Phelps

@noelbundick noelbundick added bug new new issue not yet triaged labels Jun 23, 2021
@jbardin
Copy link
Member

jbardin commented Jun 23, 2021

Hi @noelbundick,

Thanks for filing the issue. It does look like the fixup to turn the config block into an attribute is somehow causing the reference to be missed here. Since SchemaConfigModeAttr in actuality is turning the block into an attribute, using attribute syntax will allow the plan reference to be correctly evaluated:

resource "azurerm_virtual_network" "vnet1" {
  name                = "vnet1"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]

  subnet = [{
    id = null
    address_prefix = "10.0.0.0/24"
    name           = "subnet1"
    security_group = azurerm_network_security_group.nsg1.id
  }]
}

@jbardin jbardin added config confirmed a Terraform Core team member has reproduced this issue and removed new new issue not yet triaged labels Jun 23, 2021
@noelbundick
Copy link
Author

Thanks for the quick reply. Looks like that indeed fixes up the schema mismatch, but it doesn't solve my problem.

Context: I'm looking to analyze Terraform plans to better understand the semantics of what's going to happen during apply. Ex: Terraform does a great job at showing me that "NSG is going to block port 9000", but I'm looking to process that type of information to extract a higher-level insight, such as "This configuration change will block Application X from being accessed from your on-premises network", or "You've enabled HTTPS-only traffic on your storage account, but subnet X has an NSG rule that doesn't allow outbound TCP 443. This is possibly an invalid configuration"

Currently, the docs prescribe the block syntax (which functions correctly during plan/apply) and all of the configuration that I've seen out in the wild also uses block syntax - save for the explicit empty array usage to delete all subnets. So I can fix my own files, but that's not particularly useful for anyone else

There are 26 or so uses of SchemaConfigModeAttr in the Azure provider, with another half dozen each for AWS & Google. And those usages hit some common/key resources, such as virtual networks.

Intuitively, I expected the data for terraform show and terraform show -json to match. Sample outputs below

Terraform show:

$ terraform show -no-color plans/azurerm.tfplan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_network_security_group.nsg1 will be created
  + resource "azurerm_network_security_group" "nsg1" {
      + id                  = (known after apply)
      + location            = "westus2"
      + name                = "nsg1"
      + resource_group_name = "test-rg100"
      + security_rule       = [
          + {
              + access                                     = "allow"
              + description                                = "value"
              + destination_address_prefix                 = "*"
              + destination_address_prefixes               = []
              + destination_application_security_group_ids = []
              + destination_port_range                     = "80"
              + destination_port_ranges                    = []
              + direction                                  = "inbound"
              + name                                       = "allow http"
              + priority                                   = 100
              + protocol                                   = "tcp"
              + source_address_prefix                      = "*"
              + source_address_prefixes                    = []
              + source_application_security_group_ids      = []
              + source_port_range                          = "*"
              + source_port_ranges                         = []
            },
        ]
    }

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "westus2"
      + name     = "test-rg100"
    }

  # azurerm_virtual_network.vnet1 will be created
  + resource "azurerm_virtual_network" "vnet1" {
      + address_space         = [
          + "10.0.0.0/16",
        ]
      + guid                  = (known after apply)
      + id                    = (known after apply)
      + location              = "westus2"
      + name                  = "vnet1"
      + resource_group_name   = "test-rg100"
      + subnet                = [
          + {
              + address_prefix = "10.0.0.0/24"
              + id             = (known after apply)
              + name           = "subnet1"
              + security_group = (known after apply)
            },
        ]
      + vm_protection_enabled = false
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Terraform show -json:

$ terraform show -json plans/azurerm.tf
{"format_version":"0.1","terraform_version":"1.0.0","planned_values":{"root_module":{"resources":[{"address":"azurerm_network_security_group.nsg1","mode":"managed","type":"azurerm_network_security_group","name":"nsg1","provider_name":"registry.terraform.io/hashicorp/azurerm","schema_version":0,"values":{"location":"westus2","name":"nsg1","resource_group_name":"test-rg100","security_rule":[{"access":"allow","description":"value","destination_address_prefix":"*","destination_address_prefixes":[],"destination_application_security_group_ids":[],"destination_port_range":"80","destination_port_ranges":[],"direction":"inbound","name":"allow http","priority":100,"protocol":"tcp","source_address_prefix":"*","source_address_prefixes":[],"source_application_security_group_ids":[],"source_port_range":"*","source_port_ranges":[]}],"tags":null,"timeouts":null}},{"address":"azurerm_resource_group.rg","mode":"managed","type":"azurerm_resource_group","name":"rg","provider_name":"registry.terraform.io/hashicorp/azurerm","schema_version":0,"values":{"location":"westus2","name":"test-rg100","tags":null,"timeouts":null}},{"address":"azurerm_virtual_network.vnet1","mode":"managed","type":"azurerm_virtual_network","name":"vnet1","provider_name":"registry.terraform.io/hashicorp/azurerm","schema_version":0,"values":{"address_space":["10.0.0.0/16"],"bgp_community":null,"ddos_protection_plan":[],"dns_servers":null,"location":"westus2","name":"vnet1","resource_group_name":"test-rg100","subnet":[{"address_prefix":"10.0.0.0/24","name":"subnet1"}],"tags":null,"timeouts":null,"vm_protection_enabled":false}}]}},"resource_changes":[{"address":"azurerm_network_security_group.nsg1","mode":"managed","type":"azurerm_network_security_group","name":"nsg1","provider_name":"registry.terraform.io/hashicorp/azurerm","change":{"actions":["create"],"before":null,"after":{"location":"westus2","name":"nsg1","resource_group_name":"test-rg100","security_rule":[{"access":"allow","description":"value","destination_address_prefix":"*","destination_address_prefixes":[],"destination_application_security_group_ids":[],"destination_port_range":"80","destination_port_ranges":[],"direction":"inbound","name":"allow http","priority":100,"protocol":"tcp","source_address_prefix":"*","source_address_prefixes":[],"source_application_security_group_ids":[],"source_port_range":"*","source_port_ranges":[]}],"tags":null,"timeouts":null},"after_unknown":{"id":true,"security_rule":[{"destination_address_prefixes":[],"destination_application_security_group_ids":[],"destination_port_ranges":[],"source_address_prefixes":[],"source_application_security_group_ids":[],"source_port_ranges":[]}]},"before_sensitive":false,"after_sensitive":{"security_rule":[{"destination_address_prefixes":[],"destination_application_security_group_ids":[],"destination_port_ranges":[],"source_address_prefixes":[],"source_application_security_group_ids":[],"source_port_ranges":[]}]}}},{"address":"azurerm_resource_group.rg","mode":"managed","type":"azurerm_resource_group","name":"rg","provider_name":"registry.terraform.io/hashicorp/azurerm","change":{"actions":["create"],"before":null,"after":{"location":"westus2","name":"test-rg100","tags":null,"timeouts":null},"after_unknown":{"id":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"azurerm_virtual_network.vnet1","mode":"managed","type":"azurerm_virtual_network","name":"vnet1","provider_name":"registry.terraform.io/hashicorp/azurerm","change":{"actions":["create"],"before":null,"after":{"address_space":["10.0.0.0/16"],"bgp_community":null,"ddos_protection_plan":[],"dns_servers":null,"location":"westus2","name":"vnet1","resource_group_name":"test-rg100","subnet":[{"address_prefix":"10.0.0.0/24","name":"subnet1"}],"tags":null,"timeouts":null,"vm_protection_enabled":false},"after_unknown":{"address_space":[false],"ddos_protection_plan":[],"guid":true,"id":true,"subnet":[{"id":true,"security_group":true}]},"before_sensitive":false,"after_sensitive":{"address_space":[false],"ddos_protection_plan":[],"subnet":[{}]}}}],"configuration":{"provider_config":{"azurerm":{"name":"azurerm","version_constraint":"2.61.0","expressions":{"features":[{}]}}},"root_module":{"resources":[{"address":"azurerm_network_security_group.nsg1","mode":"managed","type":"azurerm_network_security_group","name":"nsg1","provider_config_key":"azurerm","expressions":{"location":{"references":["azurerm_resource_group.rg"]},"name":{"constant_value":"nsg1"},"resource_group_name":{"references":["azurerm_resource_group.rg"]}},"schema_version":0},{"address":"azurerm_resource_group.rg","mode":"managed","type":"azurerm_resource_group","name":"rg","provider_config_key":"azurerm","expressions":{"location":{"constant_value":"westus2"},"name":{"constant_value":"test-rg100"}},"schema_version":0},{"address":"azurerm_virtual_network.vnet1","mode":"managed","type":"azurerm_virtual_network","name":"vnet1","provider_config_key":"azurerm","expressions":{"address_space":{"constant_value":["10.0.0.0/16"]},"location":{"references":["azurerm_resource_group.rg"]},"name":{"constant_value":"vnet1"},"resource_group_name":{"references":["azurerm_resource_group.rg"]}},"schema_version":0}]}}}

Prettified JSON

{
  "format_version": "0.1",
  "terraform_version": "1.0.0",
  "planned_values": {
    "root_module": {
      "resources": [
        {
          "address": "azurerm_network_security_group.nsg1",
          "mode": "managed",
          "type": "azurerm_network_security_group",
          "name": "nsg1",
          "provider_name": "registry.terraform.io/hashicorp/azurerm",
          "schema_version": 0,
          "values": {
            "location": "westus2",
            "name": "nsg1",
            "resource_group_name": "test-rg100",
            "security_rule": [
              {
                "access": "allow",
                "description": "value",
                "destination_address_prefix": "*",
                "destination_address_prefixes": [],
                "destination_application_security_group_ids": [],
                "destination_port_range": "80",
                "destination_port_ranges": [],
                "direction": "inbound",
                "name": "allow http",
                "priority": 100,
                "protocol": "tcp",
                "source_address_prefix": "*",
                "source_address_prefixes": [],
                "source_application_security_group_ids": [],
                "source_port_range": "*",
                "source_port_ranges": []
              }
            ],
            "tags": null,
            "timeouts": null
          }
        },
        {
          "address": "azurerm_resource_group.rg",
          "mode": "managed",
          "type": "azurerm_resource_group",
          "name": "rg",
          "provider_name": "registry.terraform.io/hashicorp/azurerm",
          "schema_version": 0,
          "values": {
            "location": "westus2",
            "name": "test-rg100",
            "tags": null,
            "timeouts": null
          }
        },
        {
          "address": "azurerm_virtual_network.vnet1",
          "mode": "managed",
          "type": "azurerm_virtual_network",
          "name": "vnet1",
          "provider_name": "registry.terraform.io/hashicorp/azurerm",
          "schema_version": 0,
          "values": {
            "address_space": [
              "10.0.0.0/16"
            ],
            "bgp_community": null,
            "ddos_protection_plan": [],
            "dns_servers": null,
            "location": "westus2",
            "name": "vnet1",
            "resource_group_name": "test-rg100",
            "subnet": [
              {
                "address_prefix": "10.0.0.0/24",
                "name": "subnet1"
              }
            ],
            "tags": null,
            "timeouts": null,
            "vm_protection_enabled": false
          }
        }
      ]
    }
  },
  "resource_changes": [
    {
      "address": "azurerm_network_security_group.nsg1",
      "mode": "managed",
      "type": "azurerm_network_security_group",
      "name": "nsg1",
      "provider_name": "registry.terraform.io/hashicorp/azurerm",
      "change": {
        "actions": [
          "create"
        ],
        "before": null,
        "after": {
          "location": "westus2",
          "name": "nsg1",
          "resource_group_name": "test-rg100",
          "security_rule": [
            {
              "access": "allow",
              "description": "value",
              "destination_address_prefix": "*",
              "destination_address_prefixes": [],
              "destination_application_security_group_ids": [],
              "destination_port_range": "80",
              "destination_port_ranges": [],
              "direction": "inbound",
              "name": "allow http",
              "priority": 100,
              "protocol": "tcp",
              "source_address_prefix": "*",
              "source_address_prefixes": [],
              "source_application_security_group_ids": [],
              "source_port_range": "*",
              "source_port_ranges": []
            }
          ],
          "tags": null,
          "timeouts": null
        },
        "after_unknown": {
          "id": true,
          "security_rule": [
            {
              "destination_address_prefixes": [],
              "destination_application_security_group_ids": [],
              "destination_port_ranges": [],
              "source_address_prefixes": [],
              "source_application_security_group_ids": [],
              "source_port_ranges": []
            }
          ]
        },
        "before_sensitive": false,
        "after_sensitive": {
          "security_rule": [
            {
              "destination_address_prefixes": [],
              "destination_application_security_group_ids": [],
              "destination_port_ranges": [],
              "source_address_prefixes": [],
              "source_application_security_group_ids": [],
              "source_port_ranges": []
            }
          ]
        }
      }
    },
    {
      "address": "azurerm_resource_group.rg",
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "rg",
      "provider_name": "registry.terraform.io/hashicorp/azurerm",
      "change": {
        "actions": [
          "create"
        ],
        "before": null,
        "after": {
          "location": "westus2",
          "name": "test-rg100",
          "tags": null,
          "timeouts": null
        },
        "after_unknown": {
          "id": true
        },
        "before_sensitive": false,
        "after_sensitive": {}
      }
    },
    {
      "address": "azurerm_virtual_network.vnet1",
      "mode": "managed",
      "type": "azurerm_virtual_network",
      "name": "vnet1",
      "provider_name": "registry.terraform.io/hashicorp/azurerm",
      "change": {
        "actions": [
          "create"
        ],
        "before": null,
        "after": {
          "address_space": [
            "10.0.0.0/16"
          ],
          "bgp_community": null,
          "ddos_protection_plan": [],
          "dns_servers": null,
          "location": "westus2",
          "name": "vnet1",
          "resource_group_name": "test-rg100",
          "subnet": [
            {
              "address_prefix": "10.0.0.0/24",
              "name": "subnet1"
            }
          ],
          "tags": null,
          "timeouts": null,
          "vm_protection_enabled": false
        },
        "after_unknown": {
          "address_space": [
            false
          ],
          "ddos_protection_plan": [],
          "guid": true,
          "id": true,
          "subnet": [
            {
              "id": true,
              "security_group": true
            }
          ]
        },
        "before_sensitive": false,
        "after_sensitive": {
          "address_space": [
            false
          ],
          "ddos_protection_plan": [],
          "subnet": [
            {}
          ]
        }
      }
    }
  ],
  "configuration": {
    "provider_config": {
      "azurerm": {
        "name": "azurerm",
        "version_constraint": "2.61.0",
        "expressions": {
          "features": [
            {}
          ]
        }
      }
    },
    "root_module": {
      "resources": [
        {
          "address": "azurerm_network_security_group.nsg1",
          "mode": "managed",
          "type": "azurerm_network_security_group",
          "name": "nsg1",
          "provider_config_key": "azurerm",
          "expressions": {
            "location": {
              "references": [
                "azurerm_resource_group.rg"
              ]
            },
            "name": {
              "constant_value": "nsg1"
            },
            "resource_group_name": {
              "references": [
                "azurerm_resource_group.rg"
              ]
            }
          },
          "schema_version": 0
        },
        {
          "address": "azurerm_resource_group.rg",
          "mode": "managed",
          "type": "azurerm_resource_group",
          "name": "rg",
          "provider_config_key": "azurerm",
          "expressions": {
            "location": {
              "constant_value": "westus2"
            },
            "name": {
              "constant_value": "test-rg100"
            }
          },
          "schema_version": 0
        },
        {
          "address": "azurerm_virtual_network.vnet1",
          "mode": "managed",
          "type": "azurerm_virtual_network",
          "name": "vnet1",
          "provider_config_key": "azurerm",
          "expressions": {
            "address_space": {
              "constant_value": [
                "10.0.0.0/16"
              ]
            },
            "location": {
              "references": [
                "azurerm_resource_group.rg"
              ]
            },
            "name": {
              "constant_value": "vnet1"
            },
            "resource_group_name": {
              "references": [
                "azurerm_resource_group.rg"
              ]
            }
          },
          "schema_version": 0
        }
      ]
    }
  }
}

@noelbundick
Copy link
Author

In the debugger, I see that subnet is present on the body, then is stripped during the call to body.PartialContent():

content, _, _ := body.PartialContent(lowSchema)

I'm new to the codebase, so I'm not sure if the fix needs to happen before the call to PartialContent(), inside it, or as a patch after the fact. It appears to jump into hashicorp/hcl here: https://github.com/hashicorp/hcl/blob/c7ee8b78101c33b4dfed2641d78cf5e9651eabb8/hclsyntax/structure.go#L128

@jbardin
Copy link
Member

jbardin commented Jun 24, 2021

Hi @noelbundick, can you please clarify for me exactly what it is you're looking for? The original post seems to claim that what you need are the missing references from within the "subnet" block, but you then state that this doesn't solve your problem.

Now I cannot guarantee that we will be able to restore these references in all cases. SchemaConfigModeAttr was a workaround to allow compatibility with providers which relied on bugs in old versions of terraform, and is not the normal operating mode for new resources. There are a few ways this feature can cause problems, but in the long run it should be phased out as providers update resources and adopt newer SDKs. In the meantime there unfortunately will be edge cases that need to be worked around with the legacy resources using this option.

@noelbundick
Copy link
Author

@jbardin I am looking for the JSON output generated by terraform show -json to include all of the references that are present in the plan. I'm also looking for this to work with block syntax. Asking everyone globally to make changes to their Terraform configuration files to fix the terraform show -json output is the part that doesn't work for me.

From an end-user point of view, block syntax is still "correct" because that's what the docs/samples explicitly instruct them to use (https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network)

Highlights in the diff below shows the data that is present in the plan but missing from the JSON:

        {
          "address": "azurerm_virtual_network.vnet1",
          "mode": "managed",
          "type": "azurerm_virtual_network",
          "name": "vnet1",
          "provider_config_key": "azurerm",
          "expressions": {
            "address_space": {
              "constant_value": [
                "10.0.0.0/16"
              ]
            },
            "location": {
              "references": [
                "azurerm_resource_group.rg"
              ]
            },
            "name": {
              "constant_value": "vnet1"
            },
            "resource_group_name": {
              "references": [
                "azurerm_resource_group.rg"
              ]
            },
+            "subnet": [
+              {
+                "address_prefix": {
+                 "constant_value": "10.0.0.0/24"
+                },
+                "name": {
+                  "constant_value": "subnet1"
+                },
+                "security_group": {
+                 "references": [
+                    "azurerm_network_security_group.nsg1"
+                  ]
+                }
              }

@jbardin
Copy link
Member

jbardin commented Jun 24, 2021

@noelbundick sorry, I did not mean to imply that changing all configuration to attribute syntax was the final solution, but it does offer a workaround if you are managing your own config. This issue is still open and labeled as a bug.

@github-actions
Copy link

github-actions bot commented Oct 8, 2021

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug config confirmed a Terraform Core team member has reproduced this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants