Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pkg/runner/config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"log/slog"
"maps"
"net/url"
"os"
"path/filepath"
"slices"
"strings"

Expand Down Expand Up @@ -1042,6 +1044,22 @@ func (b *runConfigBuilder) processVolumeMounts() error {
return fmt.Errorf("invalid volume format: %s (%w)", volume, err)
}

// Validate source path exists on the host filesystem (CLI context only).
// Skip for Kubernetes operator context where paths are container-relative,
// and for resource URIs which are not filesystem paths.
if b.buildContext != BuildContextOperator && !strings.HasPrefix(source, "resource://") {
absSource := source
if !filepath.IsAbs(absSource) {
absSource, err = filepath.Abs(absSource)
if err != nil {
return fmt.Errorf("failed to resolve volume mount source path %q: %w", source, err)
}
}
if _, err := os.Stat(absSource); err != nil {
return fmt.Errorf("volume mount source path does not exist: %s", source)
}
}

// Check for duplicate mount target
if existingSource, isDuplicate := existingMounts[target]; isDuplicate {
slog.Warn("Skipping duplicate mount target", "target", target, "existing_source", existingSource)
Expand Down
25 changes: 19 additions & 6 deletions pkg/runner/config_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) {
// Create a mock environment variable validator
mockValidator := &mockEnvVarValidator{}

// Create temporary directories for volume mount source paths
tmpDir1 := t.TempDir()
tmpDir2 := t.TempDir()
tmpDir3 := t.TempDir()

testCases := []struct {
name string
builderOptions []RunConfigBuilderOption
Expand All @@ -263,7 +268,7 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) {
{
name: "Volumes without permission profile but with profile name",
builderOptions: []RunConfigBuilderOption{
WithVolumes([]string{"/host:/container"}),
WithVolumes([]string{tmpDir1 + ":/container"}),
WithPermissionProfileNameOrPath(permissions.ProfileNone),
},
expectError: false,
Expand All @@ -273,7 +278,7 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) {
{
name: "Read-only volume with existing profile",
builderOptions: []RunConfigBuilderOption{
WithVolumes([]string{"/host:/container:ro"}),
WithVolumes([]string{tmpDir1 + ":/container:ro"}),
WithPermissionProfile(permissions.BuiltinNoneProfile()),
},
expectError: false,
Expand All @@ -283,7 +288,7 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) {
{
name: "Read-write volume with existing profile",
builderOptions: []RunConfigBuilderOption{
WithVolumes([]string{"/host:/container"}),
WithVolumes([]string{tmpDir1 + ":/container"}),
WithPermissionProfile(permissions.BuiltinNoneProfile()),
},
expectError: false,
Expand All @@ -294,9 +299,9 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) {
name: "Multiple volumes with existing profile",
builderOptions: []RunConfigBuilderOption{
WithVolumes([]string{
"/host1:/container1:ro",
"/host2:/container2",
"/host3:/container3:ro",
tmpDir1 + ":/container1:ro",
tmpDir2 + ":/container2",
tmpDir3 + ":/container3:ro",
}),
WithPermissionProfile(permissions.BuiltinNoneProfile()),
},
Expand All @@ -312,6 +317,14 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) {
},
expectError: true,
},
{
name: "Non-existent source path",
builderOptions: []RunConfigBuilderOption{
WithVolumes([]string{"/nonexistent/path/that/does/not/exist:/container"}),
WithPermissionProfile(permissions.BuiltinNoneProfile()),
},
expectError: true,
},
}

for _, tc := range testCases {
Expand Down
Loading