hashicorp / terraform-provider-time

Utility provider that provides Time-Based Resources
https://registry.terraform.io/providers/hashicorp/time/latest
Mozilla Public License 2.0
104 stars 30 forks source link

time_rotating doesn't work with lifecycle replace_triggered_by #118

Open andyroyle opened 2 years ago

andyroyle commented 2 years ago

Community Note

Terraform Version

v1.2.7

Affected Resource(s)

Terraform Configuration Files

resource "time_rotating" "rotate" {
  rotation_minutes = 1
}

resource "local_file" "test" {
  filename = "${path.module}/file.txt"
  content  = "foo"

  lifecycle {
    replace_triggered_by = [
      time_rotating.rotate.rotation_rfc3339
    ]
  }
}

Debug Output

debug output

Expected Behavior

The local_file resource should be replaced when time_rotating.rotate expires.

Actual Behavior

When using time_rotating in the replace_triggered_by field of another resource (in this case, a local_file) the downstream resource is not replaced.

The time_rotating resource is replaced without replacing the downstream resource.

time_rotating.rotate: Refreshing state... [id=2022-08-15T12:53:48Z]
local_file.test: Refreshing state... [id=0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33]

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:

  # time_rotating.rotate will be created
  + resource "time_rotating" "rotate" {
      + day              = (known after apply)
      + hour             = (known after apply)
      + id               = (known after apply)
      + minute           = (known after apply)
      + month            = (known after apply)
      + rfc3339          = (known after apply)
      + rotation_minutes = 1
      + rotation_rfc3339 = (known after apply)
      + second           = (known after apply)
      + unix             = (known after apply)
      + year             = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
time_rotating.rotate: Creating...
time_rotating.rotate: Creation complete after 0s [id=2022-08-15T13:04:53Z]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The terraform plan shows a create for the time_rotating resource, even though it is a replace. This seems to be caused by the fact that when the time in rotation_rfc3339 has passed, the read operation removes the resource from state.

if now.After(rotationTimestamp) {
            log.Printf("[INFO] Expiration timestamp (%s) is after current timestamp (%s), removing from state", v.(string), now.Format(time.RFC3339))
            d.SetId("")
            return nil
        }

This is seen in the debug output:

2022-08-15T14:18:43.833+0100 [INFO]  provider.terraform-provider-time_v0.8.0_x5: 2022/08/15 14:18:43 [INFO] Expiration timestamp (2022-08-15T13:18:39Z) is after current timestamp (2022-08-15T13:18:43Z), removing from state: timestamp=2022-08-15T14:18:43.833+0100
2
...
2022-08-15T14:18:43.833+0100 [WARN]  Provider "registry.terraform.io/hashicorp/time" produced an unexpected new value for time_rotating.rotate during refresh.
      - Root resource was present, but now absent
2022-08-15T14:18:43.833+0100 [TRACE] NodeAbstractResouceInstance.writeResourceInstanceState to refreshState for time_rotating.rotate
2022-08-15T14:18:43.833+0100 [TRACE] NodeAbstractResouceInstance.writeResourceInstanceState: removing state object for time_rotating.rotate
2022-08-15T14:18:43.833+0100 [TRACE] Re-validating config for "time_rotating.rotate"

This causes terraform to view the operation as a create instead of a replace and so the replace_triggered_by for the downstream resource isn't triggered.

Steps to Reproduce

  1. terraform init
  2. terraform apply --auto-approve
  3. sleep 60 (wait 1 minute for the time_rotating to expire)
  4. terraform apply --auto-approve

References

kpashov commented 1 year ago

A ~dirty hack~ workaround is to use time_static to hold the value of time_rotating. This value will then be modified when time_rotating is recreated:

resource "time_rotating" "rotate" {
  rotation_minutes = 1
}

resource "time_static" "rotate" {
  rfc3339 = time_rotating.rotate.rfc3339
}

resource "local_file" "test" {
  filename = "${path.module}/file.txt"
  content  = "foo"

  lifecycle {
    replace_triggered_by = [
      time_static.rotate
    ]
  }
}
SBGoods commented 1 year ago

Hi @andyroyle thanks for raising this issue, fixing this issue requires a significant rewrite in the resource's logic and will result in a breaking change for practitioners. We will consider this issue as part of a larger effort for a Time Provider major release in the future but we do not have a timeline for this at the moment.