From cffa03007b337c6edecc52ef9b22cd6a6d9e31d9 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Wed, 17 Jun 2026 05:54:43 +0200 Subject: [PATCH] volumes: error when using relative volumes sources in compose (#353) Signed-off-by: Miek Gieben Co-authored-by: Pasha Sviderski --- pkg/client/compose/project.go | 18 +++++++++++++++++ pkg/client/compose/project_test.go | 31 +++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/pkg/client/compose/project.go b/pkg/client/compose/project.go index 0db727db..3d29d653 100644 --- a/pkg/client/compose/project.go +++ b/pkg/client/compose/project.go @@ -22,6 +22,7 @@ var registerComposeOverrides sync.Once func LoadProject(ctx context.Context, paths []string, opts ...composecli.ProjectOptionsFn) (*types.Project, error) { registerComposeOverrides.Do(func() { transform.RegisterDefaultValue("services.*.deploy.update_config", setUpdateConfigDefaults) + transform.RegisterDefaultValue("services.*.volumes.*.source", checkRelativeVolumeMount) }) defaultOpts := []composecli.ProjectOptionsFn{ @@ -119,3 +120,20 @@ func setUpdateConfigDefaults(data any, _ tree.Path, _ bool) (any, error) { } return data, nil } + +// checkRelativeVolumeMount check if a volume mount uses a relative path. +func checkRelativeVolumeMount(data any, _ tree.Path, _ bool) (any, error) { + source, ok := data.(string) + if !ok || filepath.IsAbs(source) { + return data, nil + } + // Only check actual paths, not volumes _names_ + if strings.HasPrefix(source, ".") || strings.HasPrefix(source, "~") { + // uc run also warns against this + return nil, fmt.Errorf("invalid volume mount: bind mount source '%s' is relative. If you intended to pass a host "+ + "directory or file, use absolute path, or configs might be better suited for this use case. "+ + "See https://uncloud.run/docs/concepts/configs", source) + } + + return data, nil +} diff --git a/pkg/client/compose/project_test.go b/pkg/client/compose/project_test.go index 46240c0b..73f3d2ca 100644 --- a/pkg/client/compose/project_test.go +++ b/pkg/client/compose/project_test.go @@ -189,7 +189,7 @@ REDIS_URL=redis://localhost:6379 } } -// TestLoadProject_Unsupported checks that unsupported features lead to warnings. +// TestLoadProject_Unsupported checks that unsupported features lead to warnings or errors. func TestLoadProject_Unsupported(t *testing.T) { // captureStderr runs fn while capturing stderr output and returns what was written. captureStderr := func(t *testing.T, fn func()) string { @@ -214,6 +214,7 @@ func TestLoadProject_Unsupported(t *testing.T) { composeYAML string warnCount int warnContains []string + shouldErr bool }{ { name: "unsupported dns", @@ -296,12 +297,40 @@ networks: `, warnCount: 0, }, + { + name: "relative volume sources", + composeYAML: `services: + app: + image: myapp:latest + volumes: + - ./mypath/conf:/conf:ro + - data:/var/lib/data + +volumes: + data: +`, + shouldErr: true, + }, + { + name: "home-relative volume source", + composeYAML: `services: + app: + image: myapp:latest + volumes: + - ~/conf:/conf:ro +`, + shouldErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stderr := captureStderr(t, func() { _, err := LoadProjectFromContent(context.Background(), tt.composeYAML) + if tt.shouldErr { + require.Error(t, err) + return + } require.NoError(t, err) })