Add --resolve-symlinks flag to build and push artifact commands#5724
Add --resolve-symlinks flag to build and push artifact commands#5724rohansood10 wants to merge 1 commit intofluxcd:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds a --resolve-symlinks flag to the flux build artifact and flux push artifact commands to support building OCI artifacts from directories containing symlinks, specifically addressing issue #5055 where Nix-generated symlink trees were not being properly packaged.
Changes:
- Added
--resolve-symlinksflag to both build and push artifact commands - Implemented
resolveSymlinksfunction that creates a temporary directory with symlink targets copied as regular files - Added helper function
copyFileto copy individual files - Added unit tests for the
resolveSymlinksfunction
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| cmd/flux/build_artifact.go | Added resolveSymlinks flag, implemented symlink resolution logic with resolveSymlinks and copyFile helper functions |
| cmd/flux/push_artifact.go | Added resolveSymlinks flag and integrated symlink resolution into the push command flow |
| cmd/flux/build_artifact_test.go | Added Test_resolveSymlinks unit test covering symlinked files, regular files, and symlinked directories |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Addressed the review feedback in the latest push:
|
|
Friendly ping — any chance to review this when you get a moment? Happy to make changes if needed. |
cmd/flux/build_artifact.go
Outdated
| // copyDir recursively copies the contents of srcDir to dstDir, resolving any | ||
| // symlinks encountered along the way. This handles symlinked directories that | ||
| // filepath.Walk would not descend into (since Walk uses Lstat). | ||
| func copyDir(srcDir, dstDir string) error { |
There was a problem hiding this comment.
copyDir follows symlinks recursively with no cycle detection. If a symlink creates a circular reference (e.g., link -> ../parent), filepath.EvalSymlinks resolves the intermediate symlink but the resolved real directory can contain further symlinks pointing back up, causing infinite recursion and a stack overflow.
This is particularly relevant for the Nix use case, where symlink trees can be complex. A visited map tracking resolved real paths (by device+inode or absolute path) should be threaded through the recursion:
func copyDir(srcDir, dstDir string, visited map[string]bool) error {
real, _ := filepath.EvalSymlinks(srcDir)
abs, _ := filepath.Abs(real)
if visited[abs] {
return nil // break the cycle
}
visited[abs] = true
// ... rest of the function
}
cmd/flux/build_artifact.go
Outdated
| // resolveSymlinks creates a temporary directory with symlinks resolved to their | ||
| // real file contents. This allows building artifacts from symlink trees (e.g., | ||
| // those created by Nix) where the actual files live outside the source directory. | ||
| func resolveSymlinks(srcPath string) (string, error) { |
There was a problem hiding this comment.
When resolveSymlinks receives a single file path, it returns a temp directory containing the copied file. The caller then sets path = resolved, so oci.Build now receives a directory instead of a file. While the archive content ends up functionally the same (both cases produce just the base filename in the tarball), the semantics change — and it could diverge if oci.Build behavior is ever updated.
A simpler and more correct approach for the single-file case would be to return the path to the resolved file itself (within the temp dir), not the temp dir:
// Instead of: return tmpDir, nil
return dst, nil|
Friendly ping on this PR! It's been 8 days since the last update. This PR adds the
The implementation:
All CI checks are passing. Would appreciate a review when you get a chance. Happy to address any feedback! Thanks! |
|
@rohansood10 did you read my last review?? |
|
Hi @stefanprodan, yes I did! I addressed all your review feedback in the commits from 8 days ago:
The implementation now properly handles all the edge cases you mentioned. All tests are passing. Is there any additional feedback I should address? |
|
You really don't see the last review I made around symlinks recursively with no cycle detection and the single file path? |
|
@stefanprodan Apologies for missing those — I see the two review comments now. Both are addressed in the latest push:
|
|
@rohansood10 please squash all commits into one, signoff and force push. You also need to rebase your fork. |
50edc4f to
2ee5974
Compare
|
@stefanprodan done - squashed into a single commit, signed off, and rebased onto main. Ready for another look when you get a chance! |
|
you need to sync your fork with upstream and rebase! take a look at what you pushed in this PR... |
This adds a --resolve-symlinks flag to the flux build artifact and flux push artifact commands. When enabled, symlinks in the source directory are resolved (copied as regular files/directories) before building the artifact. This includes: - Recursive symlink resolution with cycle detection - File permission preservation - Proper handling of both single-file and directory symlink targets - Comprehensive test coverage Fixes fluxcd#5055 Signed-off-by: Rohan Sood <56945243+rohansood10@users.noreply.github.com>
2ee5974 to
7bf0bda
Compare
|
@stefanprodan done - synced the fork and rebased with a single clean commit. Sorry about the messy state earlier. |
When building OCI artifacts from directories containing symlinks (e.g., symlink trees created by Nix), symlinked files are silently skipped. This adds a --resolve-symlinks flag to both build artifact and push artifact commands that resolves symlinks by copying their targets into a temp directory before archiving. Implements the approach suggested by stefanprodan in the issue discussion. Includes unit tests. Fixes #5055