OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package local | 5 package local |
6 | 6 |
7 import ( | 7 import ( |
8 "crypto/sha1" | 8 "crypto/sha1" |
9 "encoding/base64" | 9 "encoding/base64" |
10 "fmt" | 10 "fmt" |
11 "io/ioutil" | 11 "io/ioutil" |
12 "os" | 12 "os" |
13 "path/filepath" | 13 "path/filepath" |
14 "sort" | 14 "sort" |
15 "strings" | 15 "strings" |
16 "sync" | 16 "sync" |
17 "time" | |
18 | 17 |
19 "github.com/luci/luci-go/common/logging" | 18 "github.com/luci/luci-go/common/logging" |
20 | 19 |
21 "infra/tools/cipd/common" | 20 "infra/tools/cipd/common" |
22 ) | 21 ) |
23 | 22 |
24 // TODO(vadimsh): Make it work on Windows. | 23 // TODO(vadimsh): Make it work on Windows. |
25 | 24 |
26 // TODO(vadimsh): How to handle path conflicts between two packages? Currently | 25 // TODO(vadimsh): How to handle path conflicts between two packages? Currently |
27 // the last one installed wins. | 26 // the last one installed wins. |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 err = fmt.Errorf("Site root path is not provided") | 74 err = fmt.Errorf("Site root path is not provided") |
76 } else { | 75 } else { |
77 root, err = filepath.Abs(filepath.Clean(root)) | 76 root, err = filepath.Abs(filepath.Clean(root)) |
78 } | 77 } |
79 if err != nil { | 78 if err != nil { |
80 return errDeployer{err} | 79 return errDeployer{err} |
81 } | 80 } |
82 if logger == nil { | 81 if logger == nil { |
83 logger = logging.Null() | 82 logger = logging.Null() |
84 } | 83 } |
85 » return &deployerImpl{root, logger} | 84 » return &deployerImpl{NewFileSystem(root, logger), logger} |
86 } | 85 } |
87 | 86 |
88 //////////////////////////////////////////////////////////////////////////////// | 87 //////////////////////////////////////////////////////////////////////////////// |
89 // Implementation that returns error on all requests. | 88 // Implementation that returns error on all requests. |
90 | 89 |
91 type errDeployer struct{ err error } | 90 type errDeployer struct{ err error } |
92 | 91 |
93 func (d errDeployer) DeployInstance(PackageInstance) (common.Pin, error) { ret
urn common.Pin{}, d.err } | 92 func (d errDeployer) DeployInstance(PackageInstance) (common.Pin, error) { ret
urn common.Pin{}, d.err } |
94 func (d errDeployer) CheckDeployed(packageName string) (common.Pin, error) { ret
urn common.Pin{}, d.err } | 93 func (d errDeployer) CheckDeployed(packageName string) (common.Pin, error) { ret
urn common.Pin{}, d.err } |
95 func (d errDeployer) FindDeployed() (out []common.Pin, err error) { ret
urn nil, d.err } | 94 func (d errDeployer) FindDeployed() (out []common.Pin, err error) { ret
urn nil, d.err } |
96 func (d errDeployer) RemoveDeployed(packageName string) error { ret
urn d.err } | 95 func (d errDeployer) RemoveDeployed(packageName string) error { ret
urn d.err } |
97 func (d errDeployer) TempFile(prefix string) (*os.File, error) { ret
urn nil, d.err } | 96 func (d errDeployer) TempFile(prefix string) (*os.File, error) { ret
urn nil, d.err } |
98 | 97 |
99 //////////////////////////////////////////////////////////////////////////////// | 98 //////////////////////////////////////////////////////////////////////////////// |
100 // Real deployer implementation. | 99 // Real deployer implementation. |
101 | 100 |
102 // packagesDir is a subdirectory of site root to extract packages to. | 101 // packagesDir is a subdirectory of site root to extract packages to. |
103 const packagesDir = siteServiceDir + "/pkgs" | 102 const packagesDir = siteServiceDir + "/pkgs" |
104 | 103 |
105 // currentSymlink is a name of a symlink that points to latest deployed version. | 104 // currentSymlink is a name of a symlink that points to latest deployed version. |
106 const currentSymlink = "_current" | 105 const currentSymlink = "_current" |
107 | 106 |
108 // deployerImpl implements Deployer interface. | 107 // deployerImpl implements Deployer interface. |
109 type deployerImpl struct { | 108 type deployerImpl struct { |
110 » root string | 109 » fs FileSystem |
111 logger logging.Logger | 110 logger logging.Logger |
112 } | 111 } |
113 | 112 |
114 func (d *deployerImpl) DeployInstance(inst PackageInstance) (common.Pin, error)
{ | 113 func (d *deployerImpl) DeployInstance(inst PackageInstance) (common.Pin, error)
{ |
115 pin := inst.Pin() | 114 pin := inst.Pin() |
116 » d.logger.Infof("Deploying %s into %s", pin, d.root) | 115 » d.logger.Infof("Deploying %s into %s", pin, d.fs.Root()) |
117 | 116 |
118 // Be paranoid. | 117 // Be paranoid. |
119 if err := common.ValidatePin(pin); err != nil { | 118 if err := common.ValidatePin(pin); err != nil { |
120 return common.Pin{}, err | 119 return common.Pin{}, err |
121 } | 120 } |
122 » if err := d.ensureRootExists(); err != nil { | 121 » if _, err := d.fs.EnsureDirectory(d.fs.Root()); err != nil { |
123 return common.Pin{}, err | 122 return common.Pin{}, err |
124 } | 123 } |
125 | 124 |
126 // Remember currently deployed version (to remove it later). Do not frea
k out | 125 // Remember currently deployed version (to remove it later). Do not frea
k out |
127 // if it's not there (prevID is "" in that case). | 126 // if it's not there (prevID is "" in that case). |
128 oldFiles := makeStringSet() | 127 oldFiles := makeStringSet() |
129 prevID := d.findDeployedInstance(pin.PackageName, oldFiles) | 128 prevID := d.findDeployedInstance(pin.PackageName, oldFiles) |
130 | 129 |
131 // Extract new version to a final destination. | 130 // Extract new version to a final destination. |
132 newFiles := makeStringSet() | 131 newFiles := makeStringSet() |
133 destPath, err := d.deployInstance(inst, newFiles) | 132 destPath, err := d.deployInstance(inst, newFiles) |
134 if err != nil { | 133 if err != nil { |
135 return common.Pin{}, err | 134 return common.Pin{}, err |
136 } | 135 } |
137 | 136 |
138 // Switch '_current' symlink to point to a new package instance. It is a | 137 // Switch '_current' symlink to point to a new package instance. It is a |
139 // point of no return. The function must not fail going forward. | 138 // point of no return. The function must not fail going forward. |
140 mainSymlinkPath := d.packagePath(pin.PackageName, currentSymlink) | 139 mainSymlinkPath := d.packagePath(pin.PackageName, currentSymlink) |
141 » err = ensureSymlink(mainSymlinkPath, pin.InstanceID) | 140 » err = d.fs.EnsureSymlink(mainSymlinkPath, pin.InstanceID) |
142 if err != nil { | 141 if err != nil { |
143 » » ensureDirectoryGone(destPath, d.logger) | 142 » » d.fs.EnsureDirectoryGone(destPath) |
144 return common.Pin{}, err | 143 return common.Pin{}, err |
145 } | 144 } |
146 | 145 |
147 // Asynchronously remove previous version (best effort). | 146 // Asynchronously remove previous version (best effort). |
148 wg := sync.WaitGroup{} | 147 wg := sync.WaitGroup{} |
149 defer wg.Wait() | 148 defer wg.Wait() |
150 if prevID != "" && prevID != pin.InstanceID { | 149 if prevID != "" && prevID != pin.InstanceID { |
151 wg.Add(1) | 150 wg.Add(1) |
152 go func() { | 151 go func() { |
153 defer wg.Done() | 152 defer wg.Done() |
154 » » » ensureDirectoryGone(d.packagePath(pin.PackageName, prevI
D), d.logger) | 153 » » » d.fs.EnsureDirectoryGone(d.packagePath(pin.PackageName,
prevID)) |
155 }() | 154 }() |
156 } | 155 } |
157 | 156 |
158 d.logger.Infof("Adjusting symlinks for %s", pin.PackageName) | 157 d.logger.Infof("Adjusting symlinks for %s", pin.PackageName) |
159 | 158 |
160 // Make symlinks in the site directory for all new files. Reference a pa
ckage | 159 // Make symlinks in the site directory for all new files. Reference a pa
ckage |
161 // root via '_current' symlink (instead of direct destPath), to make | 160 // root via '_current' symlink (instead of direct destPath), to make |
162 // subsequent updates 'more atomic' (since they'll need to switch only | 161 // subsequent updates 'more atomic' (since they'll need to switch only |
163 // '_current' symlink to update _all_ files in the site root at once). | 162 // '_current' symlink to update _all_ files in the site root at once). |
164 d.linkFilesToRoot(mainSymlinkPath, newFiles) | 163 d.linkFilesToRoot(mainSymlinkPath, newFiles) |
165 | 164 |
166 // Delete symlinks to files no longer needed i.e. set(old) - set(new). | 165 // Delete symlinks to files no longer needed i.e. set(old) - set(new). |
167 for relPath := range oldFiles.diff(newFiles) { | 166 for relPath := range oldFiles.diff(newFiles) { |
168 » » ensureFileGone(filepath.Join(d.root, relPath), d.logger) | 167 » » d.fs.EnsureFileGone(filepath.Join(d.fs.Root(), relPath)) |
169 } | 168 } |
170 | 169 |
171 // Verify it's all right, read the manifest. | 170 // Verify it's all right, read the manifest. |
172 newPin, err := d.CheckDeployed(pin.PackageName) | 171 newPin, err := d.CheckDeployed(pin.PackageName) |
173 if err == nil && newPin.InstanceID != pin.InstanceID { | 172 if err == nil && newPin.InstanceID != pin.InstanceID { |
174 err = fmt.Errorf("Other instance (%s) was deployed concurrently"
, newPin.InstanceID) | 173 err = fmt.Errorf("Other instance (%s) was deployed concurrently"
, newPin.InstanceID) |
175 } | 174 } |
176 if err == nil { | 175 if err == nil { |
177 d.logger.Infof("Successfully deployed %s", pin) | 176 d.logger.Infof("Successfully deployed %s", pin) |
178 } else { | 177 } else { |
179 d.logger.Errorf("Failed to deploy %s: %s", pin, err) | 178 d.logger.Errorf("Failed to deploy %s: %s", pin, err) |
180 } | 179 } |
181 return newPin, err | 180 return newPin, err |
182 } | 181 } |
183 | 182 |
184 func (d *deployerImpl) CheckDeployed(pkg string) (common.Pin, error) { | 183 func (d *deployerImpl) CheckDeployed(pkg string) (common.Pin, error) { |
185 pin, err := readPackageState(d.packagePath(pkg)) | 184 pin, err := readPackageState(d.packagePath(pkg)) |
186 if err == nil && pin.PackageName != pkg { | 185 if err == nil && pin.PackageName != pkg { |
187 err = fmt.Errorf("Package path and package name in the manifest
do not match") | 186 err = fmt.Errorf("Package path and package name in the manifest
do not match") |
188 } | 187 } |
189 return pin, err | 188 return pin, err |
190 } | 189 } |
191 | 190 |
192 func (d *deployerImpl) FindDeployed() (out []common.Pin, err error) { | 191 func (d *deployerImpl) FindDeployed() (out []common.Pin, err error) { |
193 // Directories with packages are direct children of .cipd/pkgs/. | 192 // Directories with packages are direct children of .cipd/pkgs/. |
194 » pkgs := filepath.Join(d.root, filepath.FromSlash(packagesDir)) | 193 » pkgs := filepath.Join(d.fs.Root(), filepath.FromSlash(packagesDir)) |
195 infos, err := ioutil.ReadDir(pkgs) | 194 infos, err := ioutil.ReadDir(pkgs) |
196 if err != nil { | 195 if err != nil { |
197 if os.IsNotExist(err) { | 196 if os.IsNotExist(err) { |
198 err = nil | 197 err = nil |
199 return | 198 return |
200 } | 199 } |
201 return | 200 return |
202 } | 201 } |
203 | 202 |
204 // Read the package name from the package manifest. Skip broken stuff. | 203 // Read the package name from the package manifest. Skip broken stuff. |
(...skipping 17 matching lines...) Expand all Loading... |
222 // Sort by package name. | 221 // Sort by package name. |
223 sort.Strings(keys) | 222 sort.Strings(keys) |
224 out = make([]common.Pin, len(found)) | 223 out = make([]common.Pin, len(found)) |
225 for i, k := range keys { | 224 for i, k := range keys { |
226 out[i] = found[k] | 225 out[i] = found[k] |
227 } | 226 } |
228 return | 227 return |
229 } | 228 } |
230 | 229 |
231 func (d *deployerImpl) RemoveDeployed(packageName string) error { | 230 func (d *deployerImpl) RemoveDeployed(packageName string) error { |
232 » d.logger.Infof("Removing %s from %s", packageName, d.root) | 231 » d.logger.Infof("Removing %s from %s", packageName, d.fs.Root()) |
233 | 232 |
234 // Be paranoid. | 233 // Be paranoid. |
235 err := common.ValidatePackageName(packageName) | 234 err := common.ValidatePackageName(packageName) |
236 if err != nil { | 235 if err != nil { |
237 return err | 236 return err |
238 } | 237 } |
239 | 238 |
240 // Grab list of files in currently deployed package to unlink them from
root. | 239 // Grab list of files in currently deployed package to unlink them from
root. |
241 files := makeStringSet() | 240 files := makeStringSet() |
242 instanceID := d.findDeployedInstance(packageName, files) | 241 instanceID := d.findDeployedInstance(packageName, files) |
243 | 242 |
244 // If was installed, removed symlinks pointing to the package files. | 243 // If was installed, removed symlinks pointing to the package files. |
245 if instanceID != "" { | 244 if instanceID != "" { |
246 for relPath := range files { | 245 for relPath := range files { |
247 » » » ensureFileGone(filepath.Join(d.root, relPath), d.logger) | 246 » » » d.fs.EnsureFileGone(filepath.Join(d.fs.Root(), relPath)) |
248 } | 247 } |
249 } | 248 } |
250 | 249 |
251 // Ensure all garbage is gone even if instanceID == "" was returned. | 250 // Ensure all garbage is gone even if instanceID == "" was returned. |
252 » return ensureDirectoryGone(d.packagePath(packageName), d.logger) | 251 » return d.fs.EnsureDirectoryGone(d.packagePath(packageName)) |
253 } | 252 } |
254 | 253 |
255 func (d *deployerImpl) TempFile(prefix string) (*os.File, error) { | 254 func (d *deployerImpl) TempFile(prefix string) (*os.File, error) { |
256 » if err := d.ensureRootExists(); err != nil { | 255 » dir, err := d.fs.EnsureDirectory(filepath.Join(d.fs.Root(), siteServiceD
ir, "tmp")) |
257 » » return nil, err | |
258 » } | |
259 » tempPath := filepath.Join(d.root, siteServiceDir, "tmp") | |
260 » err := os.MkdirAll(tempPath, 0777) | |
261 if err != nil { | 256 if err != nil { |
262 return nil, err | 257 return nil, err |
263 } | 258 } |
264 » return ioutil.TempFile(tempPath, prefix) | 259 » return ioutil.TempFile(dir, prefix) |
265 } | 260 } |
266 | 261 |
267 //////////////////////////////////////////////////////////////////////////////// | 262 //////////////////////////////////////////////////////////////////////////////// |
268 // Utility methods. | 263 // Utility methods. |
269 | 264 |
270 // ensureRootExists makes site root directory if it is missing. | |
271 func (d *deployerImpl) ensureRootExists() error { | |
272 err := os.MkdirAll(d.root, 0777) | |
273 if err == nil || os.IsExist(err) { | |
274 return nil | |
275 } | |
276 return err | |
277 } | |
278 | |
279 // findDeployedInstance returns instanceID of a currently deployed package | 265 // findDeployedInstance returns instanceID of a currently deployed package |
280 // instance and finds all files in it (adding them to 'files' set). Returns "" | 266 // instance and finds all files in it (adding them to 'files' set). Returns "" |
281 // if nothing is deployed. File paths in 'files' are relative to package root. | 267 // if nothing is deployed. File paths in 'files' are relative to package root. |
282 func (d *deployerImpl) findDeployedInstance(pkg string, files stringSet) string
{ | 268 func (d *deployerImpl) findDeployedInstance(pkg string, files stringSet) string
{ |
283 state, err := d.CheckDeployed(pkg) | 269 state, err := d.CheckDeployed(pkg) |
284 if err != nil { | 270 if err != nil { |
285 return "" | 271 return "" |
286 } | 272 } |
287 scanPackageDir(d.packagePath(pkg, state.InstanceID), files) | 273 scanPackageDir(d.packagePath(pkg, state.InstanceID), files) |
288 return state.InstanceID | 274 return state.InstanceID |
289 } | 275 } |
290 | 276 |
291 // deployInstance atomically extracts a package instance to its final | 277 // deployInstance atomically extracts a package instance to its final |
292 // destination and returns a path to it. It writes a list of extracted files | 278 // destination and returns a path to it. It writes a list of extracted files |
293 // to 'files'. File paths in 'files' are relative to package root. | 279 // to 'files'. File paths in 'files' are relative to package root. |
294 func (d *deployerImpl) deployInstance(inst PackageInstance, files stringSet) (st
ring, error) { | 280 func (d *deployerImpl) deployInstance(inst PackageInstance, files stringSet) (st
ring, error) { |
295 // Extract new version to a final destination. ExtractPackageInstance kn
ows | 281 // Extract new version to a final destination. ExtractPackageInstance kn
ows |
296 // how to build full paths and how to atomically extract a package. No n
eed | 282 // how to build full paths and how to atomically extract a package. No n
eed |
297 // to delete garbage if it fails. | 283 // to delete garbage if it fails. |
298 destPath := d.packagePath(inst.Pin().PackageName, inst.Pin().InstanceID) | 284 destPath := d.packagePath(inst.Pin().PackageName, inst.Pin().InstanceID) |
299 » err := ExtractInstance(inst, NewFileSystemDestination(destPath)) | 285 » err := ExtractInstance(inst, NewFileSystemDestination(destPath, d.fs)) |
300 if err != nil { | 286 if err != nil { |
301 return "", err | 287 return "", err |
302 } | 288 } |
303 // Enumerate files inside. Nuke it and fail if it's unreadable. | 289 // Enumerate files inside. Nuke it and fail if it's unreadable. |
304 err = scanPackageDir(d.packagePath(inst.Pin().PackageName, inst.Pin().In
stanceID), files) | 290 err = scanPackageDir(d.packagePath(inst.Pin().PackageName, inst.Pin().In
stanceID), files) |
305 if err != nil { | 291 if err != nil { |
306 » » ensureDirectoryGone(destPath, d.logger) | 292 » » d.fs.EnsureDirectoryGone(destPath) |
307 return "", err | 293 return "", err |
308 } | 294 } |
309 return destPath, err | 295 return destPath, err |
310 } | 296 } |
311 | 297 |
312 // linkFilesToRoot makes symlinks in root that point to files in packageRoot. | 298 // linkFilesToRoot makes symlinks in root that point to files in packageRoot. |
313 // All targets are specified by 'files' as paths relative to packageRoot. This | 299 // All targets are specified by 'files' as paths relative to packageRoot. This |
314 // function is best effort and thus doesn't return errors. | 300 // function is best effort and thus doesn't return errors. |
315 func (d *deployerImpl) linkFilesToRoot(packageRoot string, files stringSet) { | 301 func (d *deployerImpl) linkFilesToRoot(packageRoot string, files stringSet) { |
316 for relPath := range files { | 302 for relPath := range files { |
317 // E.g <root>/bin/tool. | 303 // E.g <root>/bin/tool. |
318 » » symlinkAbs := filepath.Join(d.root, relPath) | 304 » » symlinkAbs := filepath.Join(d.fs.Root(), relPath) |
319 // E.g. <root>/.cipd/pkgs/name/_current/bin/tool. | 305 // E.g. <root>/.cipd/pkgs/name/_current/bin/tool. |
320 targetAbs := filepath.Join(packageRoot, relPath) | 306 targetAbs := filepath.Join(packageRoot, relPath) |
321 // E.g. ../.cipd/pkgs/name/_current/bin/tool. | 307 // E.g. ../.cipd/pkgs/name/_current/bin/tool. |
322 targetRel, err := filepath.Rel(filepath.Dir(symlinkAbs), targetA
bs) | 308 targetRel, err := filepath.Rel(filepath.Dir(symlinkAbs), targetA
bs) |
323 if err != nil { | 309 if err != nil { |
324 d.logger.Warningf("Can't get relative path from %s to %s
", filepath.Dir(symlinkAbs), targetAbs) | 310 d.logger.Warningf("Can't get relative path from %s to %s
", filepath.Dir(symlinkAbs), targetAbs) |
325 continue | 311 continue |
326 } | 312 } |
327 » » err = ensureSymlink(symlinkAbs, targetRel) | 313 » » err = d.fs.EnsureSymlink(symlinkAbs, targetRel) |
328 if err != nil { | 314 if err != nil { |
329 d.logger.Warningf("Failed to create symlink for %s", rel
Path) | 315 d.logger.Warningf("Failed to create symlink for %s", rel
Path) |
330 continue | 316 continue |
331 } | 317 } |
332 } | 318 } |
333 } | 319 } |
334 | 320 |
335 // packagePath joins paths together to return absolute path to .cipd/pkgs sub pa
th. | 321 // packagePath joins paths together to return absolute path to .cipd/pkgs sub pa
th. |
336 func (d *deployerImpl) packagePath(pkg string, rest ...string) string { | 322 func (d *deployerImpl) packagePath(pkg string, rest ...string) string { |
337 » root := filepath.Join(d.root, filepath.FromSlash(packagesDir), packageNa
meDigest(pkg)) | 323 » root := filepath.Join(d.fs.Root(), filepath.FromSlash(packagesDir), pack
ageNameDigest(pkg)) |
338 result := filepath.Join(append([]string{root}, rest...)...) | 324 result := filepath.Join(append([]string{root}, rest...)...) |
339 | 325 |
340 // Be paranoid and check that everything is inside .cipd directory. | 326 // Be paranoid and check that everything is inside .cipd directory. |
341 abs, err := filepath.Abs(result) | 327 abs, err := filepath.Abs(result) |
342 if err != nil { | 328 if err != nil { |
343 msg := fmt.Sprintf("Can't get absolute path of '%s'", result) | 329 msg := fmt.Sprintf("Can't get absolute path of '%s'", result) |
344 d.logger.Errorf("%s", msg) | 330 d.logger.Errorf("%s", msg) |
345 panic(msg) | 331 panic(msg) |
346 } | 332 } |
347 if !isSubpath(abs, root) { | 333 if !isSubpath(abs, root) { |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 manifest, err := readManifest(r) | 390 manifest, err := readManifest(r) |
405 if err != nil { | 391 if err != nil { |
406 return common.Pin{}, err | 392 return common.Pin{}, err |
407 } | 393 } |
408 return common.Pin{ | 394 return common.Pin{ |
409 PackageName: manifest.PackageName, | 395 PackageName: manifest.PackageName, |
410 InstanceID: current, | 396 InstanceID: current, |
411 }, nil | 397 }, nil |
412 } | 398 } |
413 | 399 |
414 // ensureSymlink atomically creates a symlink pointing to a target. It will | |
415 // create full directory path if necessary. | |
416 func ensureSymlink(symlink string, target string) error { | |
417 // Already set? | |
418 existing, err := os.Readlink(symlink) | |
419 if err != nil && existing == target { | |
420 return nil | |
421 } | |
422 | |
423 // Make sure path exists. | |
424 err = os.MkdirAll(filepath.Dir(symlink), 0777) | |
425 if err != nil { | |
426 return err | |
427 } | |
428 | |
429 // Create a new symlink file, can't modify existing one. | |
430 temp := fmt.Sprintf("%s_%v", symlink, time.Now().UnixNano()) | |
431 err = os.Symlink(target, temp) | |
432 if err != nil { | |
433 return err | |
434 } | |
435 | |
436 // Atomically replace current symlink with a new one. | |
437 err = os.Rename(temp, symlink) | |
438 if err != nil { | |
439 os.Remove(temp) | |
440 return err | |
441 } | |
442 | |
443 return nil | |
444 } | |
445 | |
446 // scanPackageDir finds a set of regular files (and symlinks) in a package | 400 // scanPackageDir finds a set of regular files (and symlinks) in a package |
447 // instance directory. Adds paths relative to 'dir' to 'out'. Skips package | 401 // instance directory. Adds paths relative to 'dir' to 'out'. Skips package |
448 // service directories (.cipdpkg and .cipd) since they contain package deployer | 402 // service directories (.cipdpkg and .cipd) since they contain package deployer |
449 // gut files, not something that needs to be deployed. | 403 // gut files, not something that needs to be deployed. |
450 func scanPackageDir(dir string, out stringSet) error { | 404 func scanPackageDir(dir string, out stringSet) error { |
451 return filepath.Walk(dir, func(path string, info os.FileInfo, err error)
error { | 405 return filepath.Walk(dir, func(path string, info os.FileInfo, err error)
error { |
452 if err != nil { | 406 if err != nil { |
453 return err | 407 return err |
454 } | 408 } |
455 rel, err := filepath.Rel(dir, path) | 409 rel, err := filepath.Rel(dir, path) |
456 if err != nil { | 410 if err != nil { |
457 return err | 411 return err |
458 } | 412 } |
459 if rel == packageServiceDir || rel == siteServiceDir { | 413 if rel == packageServiceDir || rel == siteServiceDir { |
460 return filepath.SkipDir | 414 return filepath.SkipDir |
461 } | 415 } |
462 if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { | 416 if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { |
463 out.add(rel) | 417 out.add(rel) |
464 } | 418 } |
465 return nil | 419 return nil |
466 }) | 420 }) |
467 } | 421 } |
468 | 422 |
469 // ensureDirectoryGone removes the directory as instantly as possible by | |
470 // renaming it first and only then recursively deleting. | |
471 func ensureDirectoryGone(path string, logger logging.Logger) error { | |
472 temp := fmt.Sprintf("%s_%v", path, time.Now().UnixNano()) | |
473 err := os.Rename(path, temp) | |
474 if err != nil { | |
475 if !os.IsNotExist(err) { | |
476 if logger != nil { | |
477 logger.Warningf("Failed to rename directory %s:
%v", path, err) | |
478 } | |
479 return err | |
480 } | |
481 return nil | |
482 } | |
483 err = os.RemoveAll(temp) | |
484 if err != nil { | |
485 if logger != nil { | |
486 logger.Warningf("Failed to remove directory %s: %v", tem
p, err) | |
487 } | |
488 return err | |
489 } | |
490 return nil | |
491 } | |
492 | |
493 // ensureFileGone removes file, logging the errors (if any). | |
494 func ensureFileGone(path string, logger logging.Logger) error { | |
495 err := os.Remove(path) | |
496 if err != nil && !os.IsNotExist(err) { | |
497 if logger != nil { | |
498 logger.Warningf("Failed to remove %s", path) | |
499 } | |
500 return err | |
501 } | |
502 return nil | |
503 } | |
504 | |
505 //////////////////////////////////////////////////////////////////////////////// | 423 //////////////////////////////////////////////////////////////////////////////// |
506 // Simple stringSet implementation for keeping a set of filenames. | 424 // Simple stringSet implementation for keeping a set of filenames. |
507 | 425 |
508 type stringSet map[string]struct{} | 426 type stringSet map[string]struct{} |
509 | 427 |
510 func makeStringSet() stringSet { | 428 func makeStringSet() stringSet { |
511 return make(stringSet) | 429 return make(stringSet) |
512 } | 430 } |
513 | 431 |
514 // add adds an element to the string set. | 432 // add adds an element to the string set. |
515 func (a stringSet) add(elem string) { | 433 func (a stringSet) add(elem string) { |
516 a[elem] = struct{}{} | 434 a[elem] = struct{}{} |
517 } | 435 } |
518 | 436 |
519 // diff returns set(a) - set(b). | 437 // diff returns set(a) - set(b). |
520 func (a stringSet) diff(b stringSet) stringSet { | 438 func (a stringSet) diff(b stringSet) stringSet { |
521 out := makeStringSet() | 439 out := makeStringSet() |
522 for elem := range a { | 440 for elem := range a { |
523 if _, ok := b[elem]; !ok { | 441 if _, ok := b[elem]; !ok { |
524 out.add(elem) | 442 out.add(elem) |
525 } | 443 } |
526 } | 444 } |
527 return out | 445 return out |
528 } | 446 } |
OLD | NEW |