feat: Add socket mount support with --mount flag#33
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements socket mount support to enable mounting Unix sockets (like Docker socket) into runner pods. It introduces a new generic mount system with a Mount type supporting DirectoryOrCreate, Directory, and Socket hostPath types, while maintaining backward compatibility with the existing CachePath functionality.
Key Changes:
- Added new
Mounttype withMountTypeenum supporting Socket, Directory, and DirectoryOrCreate types - Introduced
--mountCLI flag with extended syntax:target,src:target, orsrc:target:type - Updated templates to use generic
mount-Nnaming instead of cache-specificcache-N
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/types/types.go | Added Mount type and MountType enum; deprecated CachePath |
| pkg/types/types_test.go | Added comprehensive tests for new mount types and constants |
| internal/cmd/add.go | Added --mount flag with parsing logic; kept --cache for backward compatibility |
| pkg/templates/templates/overlay.yaml | Updated volume generation logic to handle mount types; renamed volumes to mount-N |
| pkg/templates/templates/values/schema.yaml | Added schema validation for mounts field with type constraints |
| pkg/templates/processor.go | Added mount data conversion for ytt templates |
| pkg/templates/testdata/expected/*.yaml | Updated test snapshots with new mount-N naming |
| internal/runner/template_spec/testdata/expected/*.yaml | Updated integration test snapshots |
| internal/runner/template_test.go | Updated test comments and assertions for mount-N naming |
| internal/runner/overlay_test.go | Updated test expectations for mount-N volume names |
| devbox.json | Added developer tooling scripts for testing and building |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| parts := strings.Split(path, ":") | ||
| switch len(parts) { | ||
| case 1: | ||
| // Just target path, auto-generate source | ||
| target = parts[0] | ||
| safePath := strings.TrimPrefix(path, "/") | ||
| safePath = strings.ReplaceAll(safePath, "/", "-") | ||
| source = fmt.Sprintf("/tmp/deskrun-cache/%s", safePath) | ||
| case 2: | ||
| // src:target | ||
| source = parts[0] | ||
| target = parts[1] | ||
| case 3: | ||
| // src:target:type | ||
| source = parts[0] | ||
| target = parts[1] | ||
| typeStr := parts[2] | ||
| switch typeStr { | ||
| case "DirectoryOrCreate": | ||
| mountType = types.MountTypeDirectoryOrCreate | ||
| case "Directory": | ||
| mountType = types.MountTypeDirectory | ||
| case "Socket": | ||
| mountType = types.MountTypeSocket | ||
| default: | ||
| return fmt.Errorf("invalid mount type '%s', must be one of: DirectoryOrCreate, Directory, Socket", typeStr) | ||
| } | ||
| default: | ||
| return fmt.Errorf("invalid mount format '%s', expected target, src:target, or src:target:type", path) | ||
| } |
There was a problem hiding this comment.
The mount path parsing uses strings.Split with ":" as the delimiter, which could cause issues if file paths contain colons. While colons are rare in Unix paths, they are valid characters. For example, a path like "/tmp/my:weird:path" would be incorrectly split into 4 parts.
Windows paths with drive letters (e.g., "C:/path") would also be problematic, though this seems to be a Linux/Unix-focused tool.
Consider documenting this limitation or using a more robust parsing approach that handles escaped colons or uses a different delimiter for the type field (e.g., "@" or using flags like --mount-source, --mount-target, --mount-type).
| #@ if mount_source == "": | ||
| #@ instance_num = data.values.installation.instanceNum if hasattr(data.values.installation, "instanceNum") else 0 | ||
| #@ if instance_num > 0: | ||
| #@ mount_source = "/tmp/github-runner-cache/" + data.values.installation.name + "-" + str(instance_num) + "/mount-" + str(mount_index) | ||
| #@ else: | ||
| #@ mount_source = "/tmp/github-runner-cache/" + data.values.installation.name + "/mount-" + str(mount_index) | ||
| #@ end | ||
| #@ end | ||
| #@ # Determine hostPath type based on mount type | ||
| #@ mount_type = mount.type if hasattr(mount, "type") and mount.type != "" else "DirectoryOrCreate" | ||
| #@ volumes.append({"name": "mount-" + str(mount_index), "hostPath": {"path": mount_source, "type": mount_type}}) |
There was a problem hiding this comment.
The template logic auto-generates source paths for mounts with empty sources (lines 122-129), but this doesn't make sense for Socket type mounts. When mount.type is "Socket" and mount.source is empty, the template will create a hostPath volume with type "Socket" pointing to an auto-generated path like "/tmp/github-runner-cache/.../mount-N", which won't exist as a socket.
Consider adding a check before the auto-generation logic:
if mount.type == "Socket" and mount_source == "":
# This should have been caught by CLI validation, but fail gracefully
# or skip the mount
Alternatively, ensure the CLI validation (in add.go) prevents Socket mounts with empty sources from being created in the first place.
| #@ if mount_source == "": | |
| #@ instance_num = data.values.installation.instanceNum if hasattr(data.values.installation, "instanceNum") else 0 | |
| #@ if instance_num > 0: | |
| #@ mount_source = "/tmp/github-runner-cache/" + data.values.installation.name + "-" + str(instance_num) + "/mount-" + str(mount_index) | |
| #@ else: | |
| #@ mount_source = "/tmp/github-runner-cache/" + data.values.installation.name + "/mount-" + str(mount_index) | |
| #@ end | |
| #@ end | |
| #@ # Determine hostPath type based on mount type | |
| #@ mount_type = mount.type if hasattr(mount, "type") and mount.type != "" else "DirectoryOrCreate" | |
| #@ volumes.append({"name": "mount-" + str(mount_index), "hostPath": {"path": mount_source, "type": mount_type}}) | |
| #@ # Skip invalid Socket mounts with empty sources instead of auto-generating a bogus socket path. | |
| #@ is_invalid_socket_mount = hasattr(mount, "type") and mount.type == "Socket" and mount_source == "" | |
| #@ if not is_invalid_socket_mount: | |
| #@ if mount_source == "": | |
| #@ instance_num = data.values.installation.instanceNum if hasattr(data.values.installation, "instanceNum") else 0 | |
| #@ if instance_num > 0: | |
| #@ mount_source = "/tmp/github-runner-cache/" + data.values.installation.name + "-" + str(instance_num) + "/mount-" + str(mount_index) | |
| #@ else: | |
| #@ mount_source = "/tmp/github-runner-cache/" + data.values.installation.name + "/mount-" + str(mount_index) | |
| #@ end | |
| #@ end | |
| #@ # Determine hostPath type based on mount type | |
| #@ mount_type = mount.type if hasattr(mount, "type") and mount.type != "" else "DirectoryOrCreate" | |
| #@ volumes.append({"name": "mount-" + str(mount_index), "hostPath": {"path": mount_source, "type": mount_type}}) | |
| #@ end |
- Add Mount type with MountType enum (DirectoryOrCreate, Directory, Socket) - Rename cache-N to mount-N for generic mount semantics - Add --mount flag with src:target:type syntax - Keep --cache flag for backward compatibility - Update templates to support conditional hostPath types - Add devbox scripts for testing - Update schema and overlay for new mount field Implements: #32
- Update overlay_test.go to expect mount-N instead of cache-N - Update template_test.go to use mount-0 in assertions - Regenerate test snapshots with ACCEPT_DIFF=true - Add fmt and lint scripts to devbox.json - All tests passing with make check
- Fix lint error: Remove explicit type from mountType declaration - Add validation: Socket mounts must have explicit source path - Fix examples: Remove /nix/store from --mount examples (contradicts validation) - Add validation: Prevent duplicate mount targets between --cache and --mount - Update comments: Clarify Socket mounts require explicit source - Improve error message: Clarify /nix/store validation is for mount target - Update schema: Document source field requirements for Socket types All tests passing with `make check`
This trivial change forces a fresh CI build to clear the corrupted cache that was causing false positive "Mount redeclared" errors.
The CI was failing with false positive "Mount redeclared" errors due to corrupted golangci-lint cache. Adding `go clean -cache -modcache` to both test and lint jobs ensures fresh builds and prevents cache-related type check failures. This addresses the failing checks on PR for socket mount support.
The golangci-lint-action has its own cache separate from the Go build cache. The corrupted cache with key golangci-lint.cache-Linux-2922-* was causing false "Mount redeclared" errors. Setting skip-cache: true forces golangci-lint to run without cache, ensuring clean type checks. This is the correct fix - the previous attempt to clear Go cache didn't address the golangci-lint-specific cache that was actually corrupted.
55a7b23 to
5554632
Compare
Code Review FeedbackMedium Severity1. Missing duplicate target validation within The code validates duplicates between Consider adding validation: // Check for duplicates within mounts
mountTargets := make(map[string]struct{}, len(mounts))
for _, m := range mounts {
if _, exists := mountTargets[m.Target]; exists {
return fmt.Errorf("duplicate mount target '%s' specified multiple times", m.Target)
}
mountTargets[m.Target] = struct{}{}
}Low Severity2. Socket validation is dead code ( The check for empty source with socket type can never trigger because sockets require the 3-part format which always has an explicit source. Not a bug, but the validation provides false confidence and could be simplified or removed. 3. Inconsistent auto-generated source paths
This inconsistency may confuse users migrating from Minor4. Test data references The test fixtures show mounts to |
- Add duplicate mount target validation within --mount flags - Remove dead code socket validation (unreachable due to parsing logic) - Revert skip-cache change to .github/workflows/test.yml All tests passing with devbox run test and devbox run lint
rkoster
left a comment
There was a problem hiding this comment.
I noticed that the list command doesn't display the Mounts field from runner installations.
Looking at internal/cmd/list.go:100-114, the code only displays CachePaths but is missing logic to display the Mounts field from the RunnerInstallation struct (defined in pkg/types/types.go:23).
Since mounts are a key configuration detail for understanding how runners are set up (especially for privileged modes that need Docker socket or Nix store access), it would be helpful to display them in the list output similar to how cache paths are shown.
- Display Mounts field alongside CachePaths in list output - Show mount type for non-default types (Socket, Directory) - Format matches existing CachePaths display pattern - Addresses PR feedback about missing mount visibility
✅ PR Feedback AddressedI've successfully addressed all the PR feedback as requested: 🎯 Changes Made1. Added Mounts Field Display to List Command (New in this update)Issue: The
2. Resolved All Previously Addressed Review CommentsResolved 8 review comment threads that were already addressed in previous commits:
✅ VerificationAll changes have been tested and verified: ✅ devbox run test - All 106 tests passed
- 85 cmd tests
- 12 runner tests
- 9 template spec tests
✅ devbox run lint - All linting checks passed
✅ devbox run fmt - Code properly formatted🔄 Repository State
📋 PR Status
The socket mount implementation is now complete with full |
Docker Socket Mount IssueFound the root cause of why the Root Cause: The Docker socket mount chain is broken at the Kind cluster level. While the runner configuration correctly specifies the Docker socket mount, the Kind cluster creation logic doesn't include it in the cluster's extraMounts. Current State:
Suggested Fix:
This follows the same pattern as the Nix socket mount logic but specifically for Docker socket. Would you like me to implement this fix? |
When runners use Docker socket mounts (e.g., with --mount /var/run/docker.sock:/var/run/docker.sock:Socket), the Kind cluster needs to mount the host's Docker socket into the cluster node. Without this, the runner pods fail with "hostPath type check failed: /var/run/docker.sock is not a socket file" error. This fix adds: - DetectDockerSocket() function to detect host Docker socket availability - DockerSocket field to ClusterConfig for configuring the mount - Docker socket mount to Kind cluster extraMounts when available The implementation follows the same pattern as the existing Nix socket mount logic. Fixes the issue identified in PR #33 comment about failing instant-cf-cached-privileged-kubernetes runner.
✅ Docker Socket Mount Issue FixedI've successfully addressed the root cause of the failing 🔍 Root Cause AnalysisYou correctly identified that the Docker socket mount chain was broken at the Kind cluster level. The runner configuration properly specified the Docker socket mount, but the Kind cluster creation logic didn't include it in the cluster's Error: 🛠️ ImplementationFollowing your suggested approach, I've implemented the fix with the same pattern as the existing Nix socket handling: 1. Added type ClusterConfig struct {
Name string
Network string
NixStore *ClusterMount
NixSocket *ClusterMount
DeskrunCache *ClusterMount
DockerSocket *ClusterMount // ✨ New field
}2. Created func DetectDockerSocket() *ClusterMount {
dockerSocketPath := "/var/run/docker.sock"
if _, err := os.Stat(dockerSocketPath); err == nil {
return &ClusterMount{
HostPath: dockerSocketPath,
ContainerPath: dockerSocketPath,
}
}
return nil
}3. Updated if m.config.DockerSocket != nil {
extraMounts = append(extraMounts, v1alpha4.Mount{
HostPath: m.config.DockerSocket.HostPath,
ContainerPath: m.config.DockerSocket.ContainerPath,
})
}4. Updated // Detect docker socket if available
dockerSocket := cluster.DetectDockerSocket()
clusterConfig := &types.ClusterConfig{
Name: configMgr.GetConfig().ClusterName,
NixStore: nixStore,
NixSocket: nixSocket,
DockerSocket: dockerSocket, // ✨ Configure Docker socket
}✅ Verification
🎯 How It WorksNow when
This ensures that runners configured with 📦 CommitCommit SHA: The fix is now pushed to the |
The devbox.json and devbox.lock files were accidentally converted to symlinks in a previous commit. This reverts them back to regular files matching the main branch state. Addresses PR review feedback.
✅ Devbox Files Reverted - PR UpdatedI've successfully addressed the latest PR feedback: 🎯 Changes MadeReverted devbox Files (Commit:
|
Summary
Implements socket mount support to enable mounting the host Docker socket and other Unix sockets into runner pods. This adds a generic mount system that replaces the cache-specific naming and supports different hostPath volume types.
Changes
Core Types (pkg/types/)
Mounttype withMountTypeenum supporting:DirectoryOrCreate(default) - Creates directory if missingDirectory- Mounts existing directorySocket- Mounts Unix socketCachePathfor backward compatibility - marked as deprecatedCLI (internal/cmd/)
--mountflag with syntax:target,src:target, orsrc:target:type--cacheflag for backward compatibility (marked as deprecated)Templates (pkg/templates/)
cache-Ntomount-Nfor generic semanticsmountsandcachePathsfields for backward compatibilityTests
mount-Ninstead ofcache-NExamples
Mount Docker socket for host Docker access
Mount directories with auto-generated source
Mount with explicit source and target
Breaking Changes
cache-Ntomount-Nin generated Kubernetes manifests--cachedeprecated in favor of--mountCachePathsdeprecated in favor ofMountsNote: Config migration handles old format automatically when loading existing configs.
Backward Compatibility
--cacheflag still works (deprecated)CachePathsconfig field still supportedFixes #32