hashicorp / hashicorp/terraform-plugin-sdk

Terraform apply always shows a change for unset optional boolean with default of false

Open
#673 1 comment 0 reactions 0 assignees View on GitHub
bug shims/maybe subsystem/types terraform-plugin-framework
Dominant language
Go
Stars
485
Forks
244
Avg merge
19h 57m
Merged PRs (30d)
4

Description

### SDK version

```
github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.0
```

### Relevant provider source code

```go
func guacamoleUser() *schema.Resource {
return &schema.Resource{
CreateContext: resourceUserCreate,
ReadContext: resourceUserRead,
UpdateContext: resourceUserUpdate,
DeleteContext: resourceUserDelete,
Schema: map[string]*schema.Schema{
"username": {
Type: schema.TypeString,
Description: "Username of guacamole user",
Required: true,
ForceNew: true,
},
"last_active": {
Type: schema.TypeString,
Description: "Epoch time string of last user activity",
Computed: true,
},
"attributes": {
Type: schema.TypeList,
Description: "Attributes of guacamole user",
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"organizational_role": {
Type: schema.TypeString,
Description: "Organizational role of user",
Optional: true,
Default: "",
},
"full_name": {
Type: schema.TypeString,
Description: "Full name of user",
Optional: true,
Default: "",
},
"email": {
Type: schema.TypeString,
Description: "Email of user",
Optional: true,
Default: "",
},
"expired": {
Type: schema.TypeBool,
Description: "Whether the user is expired",
Optional: true,
Default: false,
},
"timezone": {
Type: schema.TypeString,
Description: "Timezone of user",
Optional: true,
Default: "",
},
"access_window_start": {
Type: schema.TypeString,
Description: "Access window start time for user",
Optional: true,
Default: "",
},
"access_window_end": {
Type: schema.TypeString,
Description: "Access window end time for user",
Optional: true,
Default: "",
},
"disabled": {
Type: schema.TypeBool,
Description: "Whether account is disabled",
Optional: true,
Default: false,
},
"valid_from": {
Type: schema.TypeString,
Description: "Start date for when user is valid",
Optional: true,
Default: "",
},
"valid_until": {
Type: schema.TypeString,
Description: "End date for when user is valid",
Optional: true,
Default: "",
},
},
},
},
"group_membership": {
Type: schema.TypeSet,
Description: "Groups this user is a member of",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"system_permissions": {
Type: schema.TypeSet,
Description: "System permissions assigned to user",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
```

### Terraform Configuration Files

```hcl
...
```

### Debug Output

```bash
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place

Terraform will perform the following actions:

2021/01/06 08:53:23 [DEBUG] command: asking for input: "Do you want to perform these actions?"
# guacamole_user.test_user will be updated in-place
~ resource "guacamole_user" "test_user" {
id = "testUser"
# (4 unchanged attributes hidden)

- attributes {
- disabled = false -> null
- expired = false -> null
}
}

Plan: 0 to add, 1 to change, 0 to destroy.
```
### Config snippets

#### main.tf
```hcl
resource "guacamole_user" "test_user" {
username = "testUser"
}
```

#### terraform.tfstate
```json
{
"version": 4,
"terraform_version": "0.14.3",
"serial": 234,
"lineage": "06621170-99a5-4601-6744-02b6e5831bb1",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "guacamole_user",
"name": "test_user",
"provider": "provider[\"techbeck03.com/techbeck03/guacamole\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"attributes": [
{
"access_window_end": "",
"access_window_start": "",
"disabled": false,
"email": "",
"expired": false,
"full_name": "",
"organizational_role": "",
"timezone": "",
"valid_from": "",
"valid_until": ""
}
],
"group_membership": [],
"id": "testUser",
"last_active": "0",
"system_permissions": [],
"username": "testUser"
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
}
]
}
```

### Expected Behavior

On the first `terraform apply` the resource should be created and the state file should reflect the one shown above. Subsequent calls to `terraform apply` without any changes to resource definition should show no pending changes.

### Actual Behavior

On the first `terraform apply` everything works as expected and the above state file is created (and looks as expected). Subsequent calls to `terraform apply` always show the following pending changes:

```bash
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place

Terraform will perform the following actions:

2021/01/06 08:53:23 [DEBUG] command: asking for input: "Do you want to perform these actions?"
# guacamole_user.test_user will be updated in-place
~ resource "guacamole_user" "test_user" {
id = "testUser"
# (4 unchanged attributes hidden)

- attributes {
- disabled = false -> null
- expired = false -> null
}
}

Plan: 0 to add, 1 to change, 0 to destroy.
```

This happens every time i do a `terraform apply` even though nothing has changed within the resource definition.

### Steps to Reproduce

1. `terraform init`
2. `terraform apply`
3.
### References

Contributor guide

Open the contributing guide

Research direction

Start with the guacamoleUser schema and the nested attributes block shown in the issue, then reproduce the behavior with the provided main.tf and Terraform state using terraform init and terraform apply. Compare the optional boolean defaults with the plan's disabled and expired changes; done means repeated applies produce no pending changes when the configuration is unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, terraform
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.