Skip to content

Add --resolve-symlinks flag to build and push artifact commands#5724

Open
rohansood10 wants to merge 2 commits intofluxcd:mainfrom
rohansood10:feat/resolve-symlinks-5055
Open

Add --resolve-symlinks flag to build and push artifact commands#5724
rohansood10 wants to merge 2 commits intofluxcd:mainfrom
rohansood10:feat/resolve-symlinks-5055

Conversation

@rohansood10
Copy link

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

When building OCI artifacts from directories containing symlinks (e.g.,
symlink trees created by Nix), the symlinked files are silently skipped
because the underlying archive logic only handles regular files and
directories. This results in empty or incomplete artifacts.

This change adds a --resolve-symlinks flag to both 'flux build artifact'
and 'flux push artifact' commands. When set, symlinks are resolved by
copying their target contents into a temporary directory before building
the artifact. This approach:

- Preserves backward compatibility (default behavior unchanged)
- Works with symlinks pointing outside the source directory
- Handles symlinked files and directories
- Cleans up the temporary directory after the build completes

Fixes fluxcd#5055

Signed-off-by: rohansood10 <rohansood10@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-symlinks flag to both build and push artifact commands
  • Implemented resolveSymlinks function that creates a temporary directory with symlink targets copied as regular files
  • Added helper function copyFile to copy individual files
  • Added unit tests for the resolveSymlinks function

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.

Comment on lines +90 to +94
// Create a symlinked subdirectory
subDir := filepath.Join(srcDir, "subdir")
g.Expect(os.MkdirAll(subDir, 0o755)).To(Succeed())
g.Expect(os.WriteFile(filepath.Join(subDir, "nested.yaml"), []byte("nested"), 0o644)).To(Succeed())
g.Expect(os.Symlink(subDir, filepath.Join(symlinkDir, "linkeddir"))).To(Succeed())
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test creates a symlinked subdirectory with a nested file, but doesn't verify that the nested file was properly copied to the resolved directory. Add an assertion to verify that the file inside the symlinked directory is accessible and contains the expected content.

Copilot uses AI. Check for mistakes.
Comment on lines 173 to 174
if realInfo.IsDir() {
return os.MkdirAll(dstPath, realInfo.Mode())
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a symlink points to a directory, the code creates the directory but doesn't copy its contents. The filepath.Walk function doesn't descend into symlinked directories (it uses Lstat, not Stat), so files inside symlinked directories are not copied to the temporary directory. This needs to be fixed by recursively walking the resolved directory path when a symlink points to a directory.

Copilot uses AI. Check for mistakes.
Comment on lines 192 to 213
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()

if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}

out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()

if _, err := io.Copy(out, in); err != nil {
return err
}
return out.Close()
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyFile function doesn't preserve the original file's permissions. When copying files from the symlink targets, the permissions are reset to the default (0666 modified by umask) instead of preserving the source file's mode. Consider using os.Chmod to preserve the source file's permissions after copying.

Copilot uses AI. Check for mistakes.
@rohansood10 rohansood10 marked this pull request as ready for review February 22, 2026 18:03
Address review feedback:
- Fix filepath.Walk not descending into symlinked directories by using
  os.ReadDir-based recursive traversal that follows symlinks
- Preserve source file permissions in copyFile using os.OpenFile
  instead of os.Create
- Add test assertions to verify nested files in symlinked directories
  are properly copied

Signed-off-by: rohansood10 <rohansood10@users.noreply.github.com>
@rohansood10
Copy link
Author

Addressed the review feedback in the latest push:

  1. Symlinked directory traversal: Replaced filepath.Walk with a recursive copyDir function using os.ReadDir that properly follows symlinks into directories. filepath.Walk uses Lstat internally and doesn't descend into symlinked directories — the new approach resolves symlinks and recursively copies directory contents.

  2. File permission preservation: Updated copyFile to use os.OpenFile with the source file's mode instead of os.Create (which defaults to 0666), ensuring permissions are preserved.

  3. Test coverage: Added assertions to verify that files inside symlinked directories are properly copied and are regular files (not symlinks).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flux build artifact doesn't follow symlinks

2 participants