| 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 |