Skip to content

Use native buildkit symlink file action for post-install symlinks#1098

Open
cpuguy83 wants to merge 2 commits into
project-dalec:mainfrom
cpuguy83:cpuguy83/symlink-file-action
Open

Use native buildkit symlink file action for post-install symlinks#1098
cpuguy83 wants to merge 2 commits into
project-dalec:mainfrom
cpuguy83:cpuguy83/symlink-file-action

Conversation

@cpuguy83

Copy link
Copy Markdown
Collaborator

What this PR does / why we need it:

Post-install symlinks (image.post.symlinks) were created by executing a shell
script (ln -s, chown -h, chgrp -h) in the worker, mounting /etc/passwd
and /etc/group to resolve ownership by name. Buildkit now provides a native
symlink file action, so this switches to it when it is available.

Support is detected via the pb.CapFileSymlinkCreate LLB capability, mirroring
the existing diff/merge capability detection. When the connected buildkit does
not advertise the capability — or DALEC_DISABLE_SYMLINK=1 is set — Dalec falls
back to the original shell-exec implementation, so older buildkit versions keep
working.

The native path chains llb.Mkdir (for parent directories, which the symlink
action does not create itself) and llb.Symlink into a single file op resolved
against the installed rootfs. This avoids the extra exec and the /etc/passwd
/ /etc/group mounts.

Ownership semantics are preserved exactly. Chowning by user name alone would
also set the group to that user's primary group, whereas the exec fallback
leaves the unspecified side as root. To match, both user and group are always
set explicitly and an unspecified side stays 0, mirroring chown -h user /
chgrp -h group.

Which issue(s) this PR fixes (optional, using fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when the PR gets merged):
Fixes #

Special notes for your reviewer:

  • Capability detection lives in frontend/gateway.go (SupportsSymlink) and is
    wired in frontend/router.go, just like DisableDiffMerge.
  • DALEC_DISABLE_SYMLINK is registered as a known build-arg in load.go and can
    be used to force the legacy behavior.
  • Unit tests in helpers_symlink_test.go assert the marshaled LLB for both the
    native and exec paths (symlink actions, parent mkdirs, and ownership).
  • The integration test testLinuxPostInstallSymlinks runs the post-install
    symlink scenario under both paths via subtests — native by default and the
    exec fallback with DALEC_DISABLE_SYMLINK=1. The equivalent block was removed
    from the large combined container test to avoid duplicating the coverage.

Copilot AI review requested due to automatic review settings June 16, 2026 00:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR switches the post-install symlink creation (image.post.symlinks) from executing a shell script (ln -s, chown -h, chgrp -h) in a worker container to using BuildKit's native llb.Symlink file action when the connected BuildKit instance supports it (via pb.CapFileSymlinkCreate). When the capability is unavailable or DALEC_DISABLE_SYMLINK=1 is set, the original exec-based implementation is used as a fallback.

Changes:

  • Added capability detection (SupportsSymlink) in frontend/gateway.go and wired it in frontend/router.go, following the established DisableDiffMerge/SupportsDiffMerge pattern. The DALEC_DISABLE_SYMLINK build arg is registered in load.go.
  • Refactored InstallPostSymlinks in helpers.go into two internal functions—installPostSymlinksNative (BuildKit file action chain with llb.Mkdir + llb.Symlink) and installPostSymlinksExec (preserved shell-exec fallback)—with ownership semantics that explicitly set unspecified user/group to root (id 0).
  • Extracted post-install symlink integration tests from the combined container test into a dedicated testLinuxPostInstallSymlinks function that runs both native and exec fallback paths, and added comprehensive unit tests in helpers_symlink_test.go asserting the marshaled LLB output.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
frontend/gateway.go Added SupportsSymlink/checkSymlink for CapFileSymlinkCreate detection, mirroring SupportsDiffMerge
frontend/router.go Wired DisableSymlink(true) when symlink capability is not supported
helpers.go Split InstallPostSymlinks into native (llb.Symlink file action) and exec (ln -s shell script) paths; added symlinkChown helper
load.go Registered DALEC_DISABLE_SYMLINK as a known build arg
helpers_symlink_test.go New unit tests verifying marshaled LLB ops for both native and exec paths
test/linux_target_test.go Moved symlink tests to dedicated testLinuxPostInstallSymlinks with subtests for native and exec-fallback paths

Comment thread helpers.go Outdated
@cpuguy83 cpuguy83 force-pushed the cpuguy83/symlink-file-action branch from 6be4538 to 917550f Compare June 16, 2026 00:27
@cpuguy83 cpuguy83 added this to the v0.22 milestone Jun 19, 2026
@cpuguy83 cpuguy83 force-pushed the cpuguy83/symlink-file-action branch 3 times, most recently from 4de62d9 to 3b65fd1 Compare June 22, 2026 22:34
@cpuguy83 cpuguy83 self-assigned this Jun 24, 2026
@invidian

invidian commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@cpuguy83 can you resolve conflicts?

@invidian invidian left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we also add some documentation around this feature environment variable?

Comment thread helpers_symlink_test.go Outdated
Comment thread frontend/gateway.go Outdated
Comment thread helpers.go Outdated
Comment thread helpers.go Outdated
Comment thread frontend/router.go Outdated
@cpuguy83 cpuguy83 force-pushed the cpuguy83/symlink-file-action branch from 3b65fd1 to e301359 Compare July 6, 2026 23:46
@netlify

netlify Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploy Preview for dalec ready!

Name Link
🔨 Latest commit e301359
🔍 Latest deploy log https://app.netlify.com/projects/dalec/deploys/6a4c3e72420b2c0009b8157c
😎 Deploy Preview https://deploy-preview-1098--dalec.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

LLBOpsFromState is only ever called from tests that already hold a
*testing.T, so take one and assert internally via t.Fatalf instead of
returning an error. Callers drop the repeated error-check boilerplate.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
@cpuguy83

cpuguy83 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Should we also add some documentation around this feature environment variable?

The args are not so much a feature as they are a way for us to test both modes. I'd rather not document them for public use.

@cpuguy83 cpuguy83 force-pushed the cpuguy83/symlink-file-action branch 2 times, most recently from e302b20 to 80d85db Compare July 7, 2026 01:01

@invidian invidian left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just some nitpicks, overall LGTM

Comment thread frontend/support.go Outdated
Comment thread frontend/support.go Outdated
Comment thread helpers_symlink_test.go
Comment thread helpers_symlink_test.go Outdated
Post-install symlinks (image.post.symlinks) were created by executing a shell script (ln -s, chown -h, chgrp -h) in the worker, mounting /etc/passwd and /etc/group to resolve ownership. Buildkit now provides a native symlink file action, so use it when available.

Support is detected via the pb.CapFileSymlinkCreate LLB capability, mirroring the existing diff/merge detection. When the connected buildkit does not advertise the capability (or DALEC_DISABLE_SYMLINK=1 is set), Dalec falls back to the original shell-exec implementation, preserving backward compatibility with older buildkit versions.

The native path chains llb.Mkdir (for parent directories, which the symlink action does not create itself) and llb.Symlink into a single file op resolved against the installed rootfs, avoiding the extra exec plus the /etc/passwd and /etc/group mounts.

Ownership semantics are preserved exactly: chowning by user name alone would also set the group to that user's primary group, so both user and group are always set explicitly and an unspecified side stays root (id 0), matching chown -h user / chgrp -h group.

Integration tests run the post-install symlink scenario under both paths (native by default and the exec fallback via DALEC_DISABLE_SYMLINK=1) and the block is removed from the large combined container test to avoid duplication.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
@cpuguy83 cpuguy83 force-pushed the cpuguy83/symlink-file-action branch from 80d85db to 15f89c6 Compare July 8, 2026 17:53
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.

3 participants