`gitlab_project_hook`: any attribute update forces unnecessary resource replacement
## Summary Updating any attribute on `gitlab_project_hook` (e.g. setting `name` for the first time on an imported hook) forces the resource to be destroyed and recreated rather than updated in place. If `lifecycle { ignore_changes = [project_id] }` is added as a workaround to suppress the replacement, the apply then fails with: ``` Error reading GitLab project hook: Unexpected ID format (""). Expected <part1>:<part2> ``` ## Environment - Provider version: `18.11.0` (reproduced; likely affects all recent versions) - Terraform / OpenTofu: `OpenTofu ~> 1.5` ## Steps to reproduce 1. Import an existing project hook into state. 2. Set (or change) any optional attribute that differs from the value currently in state — for example, set `name` on a hook that was imported without one. 3. Run `terraform plan`. **Expected:** the change is applied as an in-place update. **Actual:** the plan proposes a destroy + recreate: ``` # gitlab_project_hook.hooks["my-project/my-hook"] must be replaced -/+ resource "gitlab_project_hook" "hooks" { + name = "my-hook" ~ project_id = 12345678 # forces replacement -> (known after apply) # forces replacement ~ hook_id = 99887766 -> (known after apply) ~ id = "12345678:99887766" -> (known after apply) # (other attributes hidden) } ``` ## Root cause `id`, `project_id`, and `hook_id` are purely computed identity attributes — set once on Create and never driven by user configuration. None of them carry a [`UseStateForUnknown`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#UseStateForUnknown) plan modifier. When any other attribute is updated, the framework marks all three as `(known after apply)`. Because `project_id` also carries `RequiresReplace`, the comparison `12345678 → (known after apply)` is treated as a potential change and triggers forced recreation — even though the project has not changed. Current schema definitions: ```go "id": schema.StringAttribute{ Computed: true, // missing: stringplanmodifier.UseStateForUnknown() }, "hook_id": schema.Int64Attribute{ Computed: true, // missing: int64planmodifier.UseStateForUnknown() }, "project_id": schema.Int64Attribute{ Computed: true, PlanModifiers: []planmodifier.Int64{ // missing: int64planmodifier.UseStateForUnknown() int64planmodifier.RequiresReplace(), }, }, ``` ## Secondary bug: `Update()` reads the resource ID from `req.Plan` The `Update` function reads the resource model from `req.Plan`: ```go func (r *gitlabProjectHookResource) Update(...) { var data *gitlabProjectHookResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) ... project, hookId, err := data.ResourceGitlabProjectHookParseId(data.ID.ValueString()) ``` Because of the root cause above, `data.ID` is `(known after apply)` in the plan, so `data.ID.ValueString()` returns `""` and `ResourceGitlabProjectHookParseId` fails with the "Unexpected ID format" error. The `Read` function correctly uses `req.State` for the same operation and serves as the reference for the fix. ## Bonus: copy-paste error in `Update` error message While in the area — the error returned on a failed `EditProjectHook` call reads `"Error creating GitLab project hook"` instead of `"Error updating GitLab project hook"`. ## Proposed fix MR incoming with fix: 1. Add `UseStateForUnknown()` to `id`, `hook_id`, and `project_id` — this is the root fix and eliminates the unnecessary recreation. 2. Fix `Update()` to read the resource ID from `req.State` instead of `req.Plan` — defence in depth; matches how `Read()` already works. 3. Fix the copy-paste error message in `Update()`.
issue