ACR remote build: PackRemoteBuildSource fails on symlinks ("is a directory"); mishandles symlink tar entries
- Dominant language
- Go
- Stars
- 569
- Forks
- 364
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 136
Description
### Output from `azd version`
`azd version 1.27.1 (commit 257908961d465e98c1a6574eae9624a7954b21cf)`
### Describe the bug
`PackRemoteBuildSource` (`cli/azd/pkg/containerregistry/pack_source.go`) — which tars the Docker build context for the **ACR remote build** path — does not handle **symlinks**. A symlink that points to a directory (e.g. `src/content/docs -> ../../docs/user`) inside the build context causes the whole pack to fail with:
```
... io.Copy ... read /src/content/docs: is a directory
```
so `azd deploy` / `azd up` fails for any container app whose context contains a symlinked directory, even though a local `docker build` of the same context succeeds.
### Root cause
In the `filepath.WalkDir` callback:
1. `filepath.WalkDir` **does not follow symlinks**, and the `DirEntry` reflects `Lstat`. For a symlink-to-dir, `d.IsDir()` is `false`, so it skips the directory branch (line ~72) and falls through to the **file** branch. There is no `info.Mode()&fs.ModeSymlink` (or `IsRegular`) guard — every non-directory entry is assumed to be a regular file.
2. `tar.FileInfoHeader(info, info.Name())` (line ~93) is also wrong for symlinks: the second argument must be the link **target** (`os.Readlink`), but the base name is passed. So even a symlink-to-*file* produces a corrupt/incorrect symlink entry.
3. `os.Open(path)` (line ~104) **follows** the symlink to the directory, then `io.Copy(tw, f)` (line ~110) does `read(2)` on a directory fd → `EISDIR` ("is a directory") → the walk returns the error and the archive is aborted.
The `.dockerignore` check runs before the open, which is why excluding the symlink path is a viable workaround.
### Expected behavior
Match `docker build`, which preserves symlinks as symlink entries in the context tar (it does not read the target directory's bytes). azd's remote-build packer should:
- Detect symlinks: `info.Mode()&fs.ModeSymlink != 0`.
- Resolve the target: `target, err := os.Readlink(path)`.
- Emit a proper header: `tar.FileInfoHeader(info, target)` + `WriteHeader`.
- **Skip** the `os.Open`/`io.Copy` body — symlink tar entries carry no payload.
With this, a symlink whose target is inside the context (the common case) resolves on the remote builder exactly as it does locally, and no `.dockerignore` workaround is needed.
While here, consider handling other irregular file types (sockets, fifos, devices) rather than assuming every non-dir entry is a regular file.
### To reproduce
1. In a container app's Docker build context, create a symlink to a directory that lives elsewhere in the context, e.g. `ln -s ../../docs/user src/content/docs`.
2. Deploy via the ACR **remote build** path (`azd deploy` / `azd up` where remote build is used).
3. Observe the failure: `... read .../src/content/docs: is a directory`.
Note: local `docker build` of the same context succeeds — the failure is specific to azd's remote-build tar packer.
### Workaround
Exclude the symlink in `.dockerignore` (the ignore check runs before the read) and recreate it inside the Dockerfile so the build still resolves it (the real target files remain in the context). The proper fix above removes the need for this.
### Notes
- No symlink test coverage currently exists in `containerregistry_test.go`; a regression test seeding a symlink-to-dir (and symlink-to-file) should be added with the fix.
Contributor guide
Assessment
This issue has not been assessed yet.