Loading...
Loading...
### Terraform Version ```shell Terraform v1.18.0-dev on darwin_arm64 ``` (Built from `main` at `f8e7458f5`. The code path is unchanged in released versions.) ### Terraform Configuration Files ```terraform output "files" { value = fileset(path.module, "*.txt") } ``` with, in the same directory: ``` real.txt # an ordinary file dangling.txt -> gone.txt # a symlink whose target does not exist ``` ### Debug Output ```console $ ls -la lrwxr-xr-x 1 user staff ... dangling.txt -> /tmp/tffileset/gone.txt -rw-r--r-- 1 user staff 3 real.txt $ echo 'fileset(path.cwd, "*.txt")' | terraform console ╷ │ Error: Error in function call │ │ on line 1: │ (source code not available) │ │ Call to function "fileset" failed: failed to stat │ "/tmp/tffileset/dangling.txt": stat /tmp/tffileset/dangling.txt: no such │ file or directory. ╵ ``` ### Expected Behavior ``` toset([ "real.txt", ]) ``` A symlink whose target is missing is not a regular file, so it cannot be a member of the result either way. Every other path that matched the pattern should still be returned. ### Actual Behavior The call fails, and **no** file is returned — `real.txt` is lost along with the broken link. One unresolvable name in a matched directory takes the whole `fileset` result with it. ### Steps to Reproduce ```console mkdir /tmp/tffileset && cd /tmp/tffileset echo hi > real.txt ln -s /tmp/tffileset/gone.txt dangling.txt echo 'fileset(path.cwd, "*.txt")' | terraform console ``` ### Additional Context `internal/lang/funcs/filesystem.go`, in `MakeFileSetFunc`: ```go for _, match := range matches { fi, err := os.Stat(match) if err != nil { return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat %s: %w", redactIfSensitive(match, marks...), err) } if !fi.Mode().IsRegular() { continue } ``` `os.Stat` follows symlinks, so a dangling link returns `fs.ErrNotExist` and the loop aborts the whole call. The very next statement would have skipped that entry anyway: a broken link is not a regular file. Two ways to hit this, neither of them exotic: - **A dangling symlink in the tree.** Vendored dependencies, build output, a checkout of a repository that carries links to files excluded by a sparse checkout, or a link into a path that only exists on another machine. Nothing needs to reference it; it only has to match the glob. - **A file removed between the glob and the stat.** `doublestar.Glob` lists the names first and the loop stats them afterwards, so anything that disappears in that window produces the same error. That makes the failure intermittent and, in CI where a parallel step is writing to the same tree, hard to attribute. In both cases the name does not resolve to anything, so it cannot be a regular file and cannot belong in the returned set. Skipping it produces exactly the set the configuration asked for. A stat that fails for another reason — a permission problem, say — is a different matter and should still surface, since the file may well exist and be readable by someone. I have that change working with a test and will open a PR referencing this issue. ### References - `internal/lang/funcs/filesystem.go` — `MakeFileSetFunc` --- Transparency, per the AI Usage section of CONTRIBUTING.md: this report was prepared with AI assistance. The reproduction above was run against a binary built from `main` and the output is copied verbatim from that run.
Click on a version to see all relevant bugs
Terraform Integration
Learn more about where this data comes from
BugZero Plan
Streamline upgrades with automated vendor bug scrubs
BugZero Prevent
Wish you caught this bug sooner? Get proactive today.