Skip to content

Commit d5c08e6

Browse files
committed
bake: handle tilde expansion in filepaths
Signed-off-by: David Karlsson <[email protected]>
1 parent 70487be commit d5c08e6

File tree

3 files changed

+286
-0
lines changed

3 files changed

+286
-0
lines changed

bake/bake.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/docker/buildx/bake/hclparser"
2222
"github.com/docker/buildx/build"
2323
"github.com/docker/buildx/util/buildflags"
24+
"github.com/docker/buildx/util/pathutil"
2425
"github.com/docker/buildx/util/platformutil"
2526
"github.com/docker/buildx/util/progress"
2627
"github.com/docker/cli/cli/config"
@@ -815,7 +816,76 @@ var (
815816
_ hclparser.WithGetName = &Group{}
816817
)
817818

819+
// expandPaths expands tilde in all path fields of the target
820+
func (t *Target) expandPaths() {
821+
// Expand context path
822+
if t.Context != nil {
823+
expanded := pathutil.ExpandTilde(*t.Context)
824+
t.Context = &expanded
825+
}
826+
827+
// Expand dockerfile path
828+
if t.Dockerfile != nil {
829+
expanded := pathutil.ExpandTilde(*t.Dockerfile)
830+
t.Dockerfile = &expanded
831+
}
832+
833+
// Expand named contexts
834+
if t.Contexts != nil {
835+
for k, v := range t.Contexts {
836+
t.Contexts[k] = pathutil.ExpandTilde(v)
837+
}
838+
}
839+
840+
// Expand secret file paths
841+
for _, s := range t.Secrets {
842+
if s.FilePath != "" {
843+
s.FilePath = pathutil.ExpandTilde(s.FilePath)
844+
}
845+
}
846+
847+
// Expand SSH key paths
848+
for _, s := range t.SSH {
849+
if len(s.Paths) > 0 {
850+
s.Paths = pathutil.ExpandTildePaths(s.Paths)
851+
}
852+
}
853+
854+
// Expand cache paths if they're local
855+
for _, c := range t.CacheFrom {
856+
if c.Type == "local" && c.Attrs != nil {
857+
if src, ok := c.Attrs["src"]; ok {
858+
c.Attrs["src"] = pathutil.ExpandTilde(src)
859+
}
860+
}
861+
}
862+
for _, c := range t.CacheTo {
863+
if c.Type == "local" && c.Attrs != nil {
864+
if dest, ok := c.Attrs["dest"]; ok {
865+
c.Attrs["dest"] = pathutil.ExpandTilde(dest)
866+
}
867+
}
868+
}
869+
870+
// Expand output paths
871+
for _, o := range t.Outputs {
872+
// Expand the Destination field
873+
if o.Destination != "" {
874+
o.Destination = pathutil.ExpandTilde(o.Destination)
875+
}
876+
// Also expand dest in Attrs if present
877+
if o.Attrs != nil {
878+
if dest, ok := o.Attrs["dest"]; ok {
879+
o.Attrs["dest"] = pathutil.ExpandTilde(dest)
880+
}
881+
}
882+
}
883+
}
884+
818885
func (t *Target) normalize() {
886+
// Expand tilde in all path fields
887+
t.expandPaths()
888+
819889
t.Annotations = removeDupesStr(t.Annotations)
820890
t.Attest = t.Attest.Normalize()
821891
t.Tags = removeDupesStr(t.Tags)

util/pathutil/resolve.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package pathutil
2+
3+
import (
4+
"os"
5+
"os/user"
6+
"path/filepath"
7+
"runtime"
8+
"strings"
9+
)
10+
11+
// ExpandTilde expands tilde in paths
12+
// - ~ expands to current user's home directory
13+
// - ~username expands to username's home directory (Unix/macOS only)
14+
// Returns original path if expansion fails or path doesn't start with ~
15+
func ExpandTilde(path string) string {
16+
if !strings.HasPrefix(path, "~") {
17+
return path
18+
}
19+
20+
// Handle ~/path or just ~
21+
if path == "~" || strings.HasPrefix(path, "~/") {
22+
home, err := os.UserHomeDir()
23+
if err != nil {
24+
return path
25+
}
26+
if path == "~" {
27+
return home
28+
}
29+
return filepath.Join(home, path[2:])
30+
}
31+
32+
// Handle ~username/path (not supported on Windows)
33+
if runtime.GOOS == "windows" {
34+
return path
35+
}
36+
37+
var username string
38+
var rest string
39+
40+
if idx := strings.Index(path, "/"); idx > 1 {
41+
username = path[1:idx]
42+
rest = path[idx+1:]
43+
} else {
44+
username = path[1:]
45+
}
46+
47+
u, err := user.Lookup(username)
48+
if err != nil {
49+
return path
50+
}
51+
52+
if rest == "" {
53+
return u.HomeDir
54+
}
55+
return filepath.Join(u.HomeDir, rest)
56+
}
57+
58+
// ExpandTildePaths expands tilde in a slice of paths
59+
func ExpandTildePaths(paths []string) []string {
60+
if paths == nil {
61+
return nil
62+
}
63+
expanded := make([]string, len(paths))
64+
for i, p := range paths {
65+
expanded[i] = ExpandTilde(p)
66+
}
67+
return expanded
68+
}

util/pathutil/resolve_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package pathutil
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"runtime"
7+
"testing"
8+
)
9+
10+
func expectedRootPath(subpath string) string {
11+
switch runtime.GOOS {
12+
case "windows":
13+
// Windows doesn't support ~username expansion
14+
return "~root/" + subpath
15+
case "darwin":
16+
return filepath.Join("/var/root", subpath)
17+
default:
18+
return filepath.Join("/root", subpath)
19+
}
20+
}
21+
22+
func TestExpandTilde(t *testing.T) {
23+
// Get current user's home directory for testing
24+
home, err := os.UserHomeDir()
25+
if err != nil {
26+
t.Fatalf("Failed to get home directory: %v", err)
27+
}
28+
29+
tests := []struct {
30+
name string
31+
input string
32+
expected string
33+
}{
34+
{
35+
name: "no tilde",
36+
input: "/absolute/path",
37+
expected: "/absolute/path",
38+
},
39+
{
40+
name: "relative path no tilde",
41+
input: "relative/path",
42+
expected: "relative/path",
43+
},
44+
{
45+
name: "just tilde",
46+
input: "~",
47+
expected: home,
48+
},
49+
{
50+
name: "tilde with path",
51+
input: "~/projects/test",
52+
expected: filepath.Join(home, "projects/test"),
53+
},
54+
{
55+
name: "tilde with dotfile",
56+
input: "~/.npmrc",
57+
expected: filepath.Join(home, ".npmrc"),
58+
},
59+
{
60+
name: "invalid username",
61+
input: "~nonexistentuser99999/path",
62+
expected: "~nonexistentuser99999/path", // Should return original
63+
},
64+
{
65+
name: "root user home",
66+
input: "~root/foo",
67+
expected: expectedRootPath("foo"),
68+
},
69+
{
70+
name: "empty path",
71+
input: "",
72+
expected: "",
73+
},
74+
{
75+
name: "special prefixes not affected",
76+
input: "docker-image://something",
77+
expected: "docker-image://something",
78+
},
79+
{
80+
name: "git url not affected",
81+
input: "[email protected]:user/repo.git",
82+
expected: "[email protected]:user/repo.git",
83+
},
84+
}
85+
86+
for _, tt := range tests {
87+
t.Run(tt.name, func(t *testing.T) {
88+
result := ExpandTilde(tt.input)
89+
if result != tt.expected {
90+
t.Errorf("ExpandTilde(%q) = %q, want %q", tt.input, result, tt.expected)
91+
}
92+
})
93+
}
94+
}
95+
96+
func TestExpandTildePaths(t *testing.T) {
97+
home, err := os.UserHomeDir()
98+
if err != nil {
99+
t.Fatalf("Failed to get home directory: %v", err)
100+
}
101+
102+
tests := []struct {
103+
name string
104+
input []string
105+
expected []string
106+
}{
107+
{
108+
name: "nil input",
109+
input: nil,
110+
expected: nil,
111+
},
112+
{
113+
name: "empty slice",
114+
input: []string{},
115+
expected: []string{},
116+
},
117+
{
118+
name: "mixed paths",
119+
input: []string{"~/path1", "/absolute/path", "relative/path", "~/.ssh/id_rsa"},
120+
expected: []string{filepath.Join(home, "path1"), "/absolute/path", "relative/path", filepath.Join(home, ".ssh/id_rsa")},
121+
},
122+
{
123+
name: "all tildes",
124+
input: []string{"~/a", "~/b", "~/c"},
125+
expected: []string{filepath.Join(home, "a"), filepath.Join(home, "b"), filepath.Join(home, "c")},
126+
},
127+
{
128+
name: "with invalid usernames",
129+
input: []string{"~/valid", "~invaliduser/path", "~/another"},
130+
expected: []string{filepath.Join(home, "valid"), "~invaliduser/path", filepath.Join(home, "another")},
131+
},
132+
}
133+
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
result := ExpandTildePaths(tt.input)
137+
if len(result) != len(tt.expected) {
138+
t.Errorf("ExpandTildePaths(%v) returned %d items, want %d", tt.input, len(result), len(tt.expected))
139+
return
140+
}
141+
for i := range result {
142+
if result[i] != tt.expected[i] {
143+
t.Errorf("ExpandTildePaths[%d] = %q, want %q", i, result[i], tt.expected[i])
144+
}
145+
}
146+
})
147+
}
148+
}

0 commit comments

Comments
 (0)