-
Notifications
You must be signed in to change notification settings - Fork 290
/
path_mapping.go
197 lines (173 loc) · 4.92 KB
/
path_mapping.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package build
import (
"context"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/tilt-dev/tilt/internal/ospath"
"github.com/tilt-dev/tilt/pkg/model"
)
// PathMapping represents a mapping from the local path to the tarball path
//
// To send a local file into a container, we copy it into a tarball, send the
// tarball to docker, and then run a sequence of steps to unpack the tarball in
// the container file system.
//
// That means every file has 3 paths:
// 1) LocalPath
// 2) TarballPath
// 3) ContainerPath
//
// In incremental builds, TarballPath and ContainerPath are always the
// same, so it was correct to use TarballPath and ContainerPath interchangeably.
//
// In DockerBuilds, this is no longer the case.
//
// TODO(nick): Do a pass on renaming all the path types
type PathMapping struct {
LocalPath string
ContainerPath string
}
func (m PathMapping) PrettyStr() string {
return fmt.Sprintf("'%s' --> '%s'", m.LocalPath, m.ContainerPath)
}
func (m PathMapping) Filter(matcher model.PathMatcher) ([]PathMapping, error) {
result := make([]PathMapping, 0)
err := filepath.WalkDir(m.LocalPath, func(currentLocal string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}
match, err := matcher.Matches(currentLocal)
if err != nil {
return err
}
if !match {
return nil
}
rpLocal, err := filepath.Rel(m.LocalPath, currentLocal)
if err != nil {
return err
}
result = append(result, PathMapping{
LocalPath: currentLocal,
ContainerPath: path.Join(m.ContainerPath, filepath.ToSlash(rpLocal)),
})
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
func FilterMappings(mappings []PathMapping, matcher model.PathMatcher) ([]PathMapping, error) {
result := make([]PathMapping, 0)
for _, mapping := range mappings {
filtered, err := mapping.Filter(matcher)
if err != nil {
return nil, err
}
result = append(result, filtered...)
}
return result, nil
}
// FilesToPathMappings converts a list of absolute local filepaths into pathMappings (i.e.
// associates local filepaths with their syncs and destination paths), returning those
// that it cannot associate with a sync.
func FilesToPathMappings(files []string, syncs []model.Sync) ([]PathMapping, []string, error) {
pms := make([]PathMapping, 0, len(files))
pathsMatchingNoSync := []string{}
for _, f := range files {
pm, couldMap, err := fileToPathMapping(f, syncs)
if err != nil {
return nil, nil, err
}
if couldMap {
pms = append(pms, pm)
} else {
pathsMatchingNoSync = append(pathsMatchingNoSync, f)
}
}
return pms, pathsMatchingNoSync, nil
}
func fileToPathMapping(file string, sync []model.Sync) (pm PathMapping, couldMap bool, err error) {
for _, s := range sync {
// Open Q: can you sync files inside of syncs?! o_0
// TODO(maia): are symlinks etc. gonna kick our asses here? If so, will
// need ospath.RealChild -- but then can't deal with deleted local files.
relPath, isChild := ospath.Child(s.LocalPath, file)
if isChild {
localPathIsFile, err := isFile(s.LocalPath)
if err != nil {
return PathMapping{}, false, fmt.Errorf("error stat'ing: %v", err)
}
var containerPath string
if endsWithUnixSeparator(s.ContainerPath) && localPathIsFile {
fileName := filepath.Base(s.LocalPath)
containerPath = path.Join(s.ContainerPath, fileName)
} else {
containerPath = path.Join(s.ContainerPath, filepath.ToSlash(relPath))
}
return PathMapping{
LocalPath: file,
ContainerPath: containerPath,
}, true, nil
}
}
// The file doesn't match any sync src's.
return PathMapping{}, false, nil
}
func endsWithUnixSeparator(path string) bool {
return strings.HasSuffix(path, "/")
}
func isFile(path string) (bool, error) {
fi, err := os.Stat(path)
if err != nil {
return false, err
}
mode := fi.Mode()
return !mode.IsDir(), nil
}
func SyncsToPathMappings(syncs []model.Sync) []PathMapping {
pms := make([]PathMapping, len(syncs))
for i, s := range syncs {
pms[i] = PathMapping{
LocalPath: s.LocalPath,
ContainerPath: s.ContainerPath,
}
}
return pms
}
// Return all the path mappings for local paths that do not exist.
func MissingLocalPaths(ctx context.Context, mappings []PathMapping) (missing, rest []PathMapping, err error) {
for _, mapping := range mappings {
_, err := os.Stat(mapping.LocalPath)
if err == nil {
rest = append(rest, mapping)
continue
}
if os.IsNotExist(err) {
missing = append(missing, mapping)
} else {
return nil, nil, errors.Wrap(err, "MissingLocalPaths")
}
}
return missing, rest, nil
}
func PathMappingsToContainerPaths(mappings []PathMapping) []string {
res := make([]string, len(mappings))
for i, m := range mappings {
res[i] = m.ContainerPath
}
return res
}
func PathMappingsToLocalPaths(mappings []PathMapping) []string {
res := make([]string, len(mappings))
for i, m := range mappings {
res[i] = m.LocalPath
}
return res
}