-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathloader.go
215 lines (186 loc) · 5.02 KB
/
loader.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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package loader
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"time"
"github.com/otiai10/copy"
)
type Loader interface {
Load() error
Paths() []string
RelPaths() (string, []string)
ContainsRelPath(string) bool
Cleanup() error
MostRecentlyModified() (string, time.Time)
}
type RecursiveManifestDirectoryLoader struct {
fromPath string
tempDir string
files []fileWithModTime
relPaths map[string]string
}
func NewRecursiveManifestDirectoryLoader(path string) Loader {
return &RecursiveManifestDirectoryLoader{fromPath: path}
}
func (l *RecursiveManifestDirectoryLoader) Load() error {
tempDir, err := mkdirTemp()
if err != nil {
return err
}
l.tempDir = tempDir
if filepath.IsAbs(l.fromPath) {
relPath, err := filepath.Rel("", l.fromPath)
if err != nil {
return err
}
l.fromPath = relPath
}
files, err := getFiles(l.fromPath)
if err != nil {
return err
}
l.relPaths = make(map[string]string, len(files))
for _, f := range files {
relPath, err := filepath.Rel(l.fromPath, f.path)
if err != nil {
return err
}
l.relPaths[relPath] = f.path
}
// if useKustomize(l.fromPath) || useKustomize(files...) {
// // it's possible that kustomization references files out of the tempDir
// // this will do `kustomize build` on l.fromPath and write the result into l.tempDir
// // TODO: this assumes all files are to be handled by kustomize,
// // it would be sensible to enable using different handlers for different subdirs
// return fmt.Errorf("TODO: implement kustomize build")
// }
copyOptions := copy.Options{
// TODO: documentation for PreserveTimes says there is limited accuracy on Linux,
// namely it's only to up to 1ms, it's possible that it need a fix, e.g. by rouding
// stored timestamps to milliseconds on all platforms;
// the answer really depends on how accurately mtime can be preserved between platforms,
// it makes sense to consider what git, tar and rsync do in that regard
PreserveTimes: true,
Skip: func(fi fs.FileInfo, src, _ string) (bool, error) {
if fi.IsDir() {
return false, nil
}
return ignoreFile(src), nil
},
}
if err := copy.Copy(l.fromPath, l.tempDir, copyOptions); err != nil {
return err
}
files, err = getFiles(l.tempDir)
if err != nil {
return err
}
l.files = files
return nil
}
func (l *RecursiveManifestDirectoryLoader) MostRecentlyModified() (string, time.Time) {
return l.files[0].path, l.files[0].time
}
func (l *RecursiveManifestDirectoryLoader) Paths() []string {
paths := make([]string, 0, len(l.files))
for _, file := range l.files {
paths = append(paths, file.path)
}
return paths
}
func (l *RecursiveManifestDirectoryLoader) RelPaths() (string, []string) {
relPaths := make([]string, 0, len(l.relPaths))
for p := range l.relPaths {
relPaths = append(relPaths, p)
}
return l.tempDir, relPaths
}
func (l *RecursiveManifestDirectoryLoader) ContainsRelPath(p string) bool {
_, ok := l.relPaths[p]
return ok
}
func (l *RecursiveManifestDirectoryLoader) Cleanup() error {
if l.tempDir == "" {
return nil
}
return os.RemoveAll(l.tempDir)
}
func mkdirTemp() (string, error) {
tempDir, err := os.MkdirTemp("", "bpt-manifest-loader-*")
if err != nil {
return "", err
}
tempDir, err = filepath.EvalSymlinks(tempDir)
if err != nil {
return "", fmt.Errorf("error evaluating symlink: %w", err)
}
return tempDir, nil
}
type fileWithModTime struct {
// collect timestamps to use for setting the artefact creation time
path string
time time.Time
}
// based on ExpandPathsToFileVisitors (https://github.com/kubernetes/cli-runtime/blob/022795328092ecd88b713a2bab868e3994eb0b87/pkg/resource/visitor.go#L478)
func getFiles(path string) ([]fileWithModTime, error) {
files := []fileWithModTime{}
fi, err := os.Stat(path)
if os.IsNotExist(err) {
return nil, fmt.Errorf("the path %q does not exist: %w", path, err)
}
if err != nil {
return nil, fmt.Errorf("the path %q cannot be accessed: %v", path, err)
}
if !fi.IsDir() {
files = append(files, fileWithModTime{path: path, time: fi.ModTime()})
return files, nil
}
doWalk := func(p string, e fs.DirEntry, err error) error {
if err != nil {
return err
}
if e.IsDir() || ignoreFile(p) {
return nil
}
info, err := e.Info()
if err != nil {
return err
}
files = append(files, fileWithModTime{path: p, time: info.ModTime()})
return nil
}
if err := filepath.WalkDir(path, doWalk); err != nil {
return nil, err
}
if len(files) == 0 {
return nil, fmt.Errorf("no files found in %q", path)
}
slices.SortFunc(files, func(a, b fileWithModTime) int {
if timewise := a.time.Compare(b.time); timewise != 0 {
return timewise
}
return strings.Compare(a.path, b.path)
})
return files, nil
}
func ignoreFile(path string) bool {
switch filepath.Ext(path) {
case ".json", ".yaml", ".yml":
return false
default:
return true
}
}
func useKustomize(paths ...string) bool {
for i := range paths {
switch filepath.Base(paths[i]) {
case "kustomization.yaml", "kustomization.yml", "Kustomization":
return true
}
}
return false
}