fix: remove repository file from state when its commit no longer exists - #3587
Conversation
resourceGithubRepositoryFileRead surfaced a 404 from the commit lookup, and
getFileCommit's 'no commit contains the file' case, as read failures. Terraform's
contract for read is to clear the ID when the remote object is gone, which the
other repository resources already do. Erroring instead leaves the resource
unable to refresh or be removed without editing state by hand.
Wrap getFileCommit's exhaustion case in an errFileCommitNotFound sentinel so the
read can distinguish it from a genuine failure, and translate both it and a
GitHub 404 into d.SetId(""). Every other error keeps propagating unchanged.
|
👋 Hi, and thank you for this contribution! This repo is maintained by GitHub and community members on a best-effort basis. We'll get to this as soon as we can. You can help us prioritize by joining the discussion on open issues and PRs, sharing details on the changes you need, and reviewing other contributions. 🤖 This is an automated message. |
|
|
||
| client := mustCreateTestGitHubClient(t, ts.URL+"/") | ||
|
|
||
| _, err := getFileCommit(context.Background(), client, "owner", "repo", "some/file.txt", "main") |
There was a problem hiding this comment.
Please use t.Context() instead
There was a problem hiding this comment.
Pull request overview
These provider review instructions are being used.
The PR aims to handle unreachable repository-file commits during Terraform refresh. However, it can remove an existing file from state and lacks resource-level regression coverage.
Changes:
- Adds an
errFileCommitNotFoundsentinel. - Clears state for missing commits.
- Adds sentinel-wrapping coverage.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
github/util_repo.go |
Adds and wraps the commit-not-found sentinel. |
github/util_repo_test.go |
Tests sentinel detection. |
github/resource_github_repository_file.go |
Handles commit lookup failures during read. |
Suppressed comments (1)
github/resource_github_repository_file.go:346
- This sentinel also does not establish that the managed file is gone:
getFileCommitis called only afterGetContentsreturned the current file successfully. Removing the ID here therefore schedules creation of a file that still exists and normally fails unlessoverwrite_on_createis enabled. Preserve the resource in state and propagate/handle the commit-metadata lookup failure instead; absence is already handled by the earlier contents lookup.
if errors.Is(err, errFileCommitNotFound) {
tflog.Info(ctx, "Removing repository file from state because no commit contains it")
d.SetId("")
return nil
| if ghErr, ok := errors.AsType[*github.ErrorResponse](err); ok && ghErr.Response != nil && ghErr.Response.StatusCode == http.StatusNotFound { | ||
| tflog.Info(ctx, "Removing repository file from state because its commit no longer exists in GitHub") | ||
| d.SetId("") |
| _, err := getFileCommit(context.Background(), client, "owner", "repo", "some/file.txt", "main") | ||
| if err == nil { | ||
| t.Fatal("expected an error when no commit contains the file, got nil") |
Resolves #3586
Before the change?
resourceGithubRepositoryFileReadreturns a hard error when the file's commit can no longer be found. Both lookup paths fall through to a barereturn diag.FromErr(err): a GitHub 404 fromGetCommit(used whencommit_shais in state), andgetFileCommit'scannot find file ...case (used when it is not).resource_github_repository_custom_property.go,resource_github_repository_ruleset.goandresource_github_branch_default.goalready detect a 404 on read and calld.SetId("").After the change?
getFileCommit's exhaustion case is wrapped in anerrFileCommitNotFoundsentinel so the read can tell it apart from a genuine failure without string matching. That case is reachable independently of any HTTP status, which is why a sentinel is used rather than only checking for a 404.Pull request checklist
Schema migrations have been created if needed— no schema change; this only affects read behaviour.Docs have been reviewed and added / updated if needed— no user-facing schema or option change, so I did not find anything inwebsite/that describes this behaviour. Happy to document it if you would like it called out.Does this introduce a breaking change?
A configuration whose file still exists is unaffected. The change only alters what happens when the commit is already gone, where the previous outcome was a permanent read error.
Testing
Test_getFileCommit_NoCommitContainsFiledrivesgetFileCommitagainst anhttptestserver that returns an empty commit list, and assertserrors.Is(err, errFileCommitNotFound). It uses the existingmustCreateTestGitHubClienthelper and needs no credentials. I verified it discriminates: removing the%wwrap makes it fail witherrors.Is(...) = false, which is the regression that would silently restore the old behaviour.I could not exercise the resource-level 404 path, since the acceptance tests need a live org and credentials I do not have. Note that
go test ./github/...does not pass cleanly on a fresh checkout in my environment either —TestAccGithubActionsOrganizationPermissionspanics without credentials — and I confirmed that failure is identical with and without this change.go build ./...,go vet ./github/...andgofmt -lare clean.AI use
Per the AI Use Policy: this change was written with AI assistance. I have reviewed it, and the verification described above is what I actually ran rather than a summary of intent. The reasoning for the sentinel over a status-code-only check, and the scope limits above, are stated deliberately — please push back if you would rather solve it differently, for example by handling the exhaustion case at the call site instead.
🤖