Chromium Code Reviews| 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 cipd | 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" | 17 "time" |
| 18 | |
| 19 "infra/libs/logging" | |
| 20 "infra/tools/cipd/common" | |
| 18 ) | 21 ) |
| 19 | 22 |
| 20 // TODO(vadimsh): Make it work on Windows, verify it works on Mac. | 23 // TODO(vadimsh): Make it work on Windows. |
| 21 | 24 |
| 22 // TODO(vadimsh): How to handle path conflicts between two packages? Currently | 25 // TODO(vadimsh): How to handle path conflicts between two packages? Currently |
| 23 // the last one installed wins. | 26 // the last one installed wins. |
| 24 | 27 |
| 25 // File system layout of a site directory <root>: | 28 // File system layout of a site directory <root>: |
| 26 // <root>/.cipd/pkgs/ | 29 // <root>/.cipd/pkgs/ |
| 27 // <package name digest>/ | 30 // <package name digest>/ |
| 28 // _current -> symlink to fea3ab83440e9dfb813785e16d4101f331ed44f4 | 31 // _current -> symlink to fea3ab83440e9dfb813785e16d4101f331ed44f4 |
| 29 // fea3ab83440e9dfb813785e16d4101f331ed44f4/ | 32 // fea3ab83440e9dfb813785e16d4101f331ed44f4/ |
| 30 // bin/ | 33 // bin/ |
| 31 // tool | 34 // tool |
| 32 // ... | 35 // ... |
| 33 // ... | 36 // ... |
| 34 // bin/ | 37 // bin/ |
| 35 // tool -> symlink to ../.cipd/pkgs/<package name digest>/_current/bin/tool | 38 // tool -> symlink to ../.cipd/pkgs/<package name digest>/_current/bin/tool |
| 36 // ... | 39 // ... |
| 37 // | 40 // |
| 38 // Where <package name digest> is derived from a package name. It doesn't have | 41 // Where <package name digest> is derived from a package name. It doesn't have |
| 39 // to be reversible though, since the package name is still stored in the | 42 // to be reversible though, since the package name is still stored in the |
| 40 // installed package manifest and can be read from there. | 43 // installed package manifest and can be read from there. |
| 41 // | 44 // |
| 42 // Some efforts are made to make sure that during the deployment a window of | 45 // Some efforts are made to make sure that during the deployment a window of |
| 43 // inconsistency in the file system is as small as possible. | 46 // inconsistency in the file system is as small as possible. |
| 44 | 47 |
| 45 // Subdirectory of site root to extract packages to. | 48 // packagesDir is a subdirectory of site root to extract packages to. |
| 46 const packagesDir = siteServiceDir + "/pkgs" | 49 const packagesDir = SiteServiceDir + "/pkgs" |
| 47 | 50 |
| 48 // Name of a symlink that points to latest deployed version. | 51 // currentSymlink is a name of a symlink that points to latest deployed version. |
| 49 const currentSymlink = "_current" | 52 const currentSymlink = "_current" |
| 50 | 53 |
| 51 // PackageState contains information about single deployed package. | 54 // log is logger to use for logs produced by this file. |
| 52 type PackageState struct { | 55 // TODO(vadimsh): Propagate as parameter. |
| 53 » // PackageName identifies the package. | 56 var log = logging.DefaultLogger |
| 54 » PackageName string | |
| 55 » // InstanceID is ID of the installed package instance (SHA1 of package c ontents). | |
| 56 » InstanceID string | |
| 57 } | |
| 58 | 57 |
| 59 // DeployInstance installs a specific instance of a package (identified by | 58 // DeployInstance installs a specific instance of a package into a site root |
| 60 // InstanceID()) into a site root directory. It unpacks the package into | 59 // directory. It unpacks the package into <root>/.cipd/pkgs/*, and rearranges |
| 61 // <root>/.cipd/pkgs/*, and rearranges symlinks to point to unpacked files. | 60 // symlinks to point to unpacked files. It tries to make it as "atomic" as |
| 62 // It tries to make it as "atomic" as possibly. | 61 // possible. Returns information about deployed instance ID. |
|
nodir
2015/05/13 01:25:29
Returns information about the deployed instance
Vadim Sh.
2015/05/13 03:08:06
Done.
| |
| 63 func DeployInstance(root string, inst PackageInstance) (state PackageState, err error) { | 62 func DeployInstance(root string, inst PackageInstance) (common.Pin, error) { |
| 64 » root, err = filepath.Abs(filepath.Clean(root)) | 63 » root, err := filepath.Abs(filepath.Clean(root)) |
| 65 if err != nil { | 64 if err != nil { |
| 66 » » return | 65 » » return common.Pin{}, err |
| 67 } | 66 } |
| 68 » log.Infof("Deploying %s:%s into %s", inst.PackageName(), inst.InstanceID (), root) | 67 » pin := inst.Pin() |
| 68 » log.Infof("Deploying %s into %s", pin, root) | |
| 69 | 69 |
| 70 // Be paranoid. | 70 // Be paranoid. |
| 71 » err = ValidatePackageName(inst.PackageName()) | 71 » err = common.ValidatePin(pin) |
| 72 if err != nil { | 72 if err != nil { |
| 73 » » return | 73 » » return common.Pin{}, err |
| 74 » } | |
| 75 » err = ValidateInstanceID(inst.InstanceID()) | |
| 76 » if err != nil { | |
| 77 » » return | |
| 78 } | 74 } |
| 79 | 75 |
| 80 // Remember currently deployed version (to remove it later). Do not frea k out | 76 // Remember currently deployed version (to remove it later). Do not frea k out |
| 81 // if it's not there (prevID is "" in that case). | 77 // if it's not there (prevID is "" in that case). |
| 82 oldFiles := makeStringSet() | 78 oldFiles := makeStringSet() |
| 83 » prevID := findDeployedInstance(root, inst.PackageName(), oldFiles) | 79 » prevID := findDeployedInstance(root, pin.PackageName, oldFiles) |
| 84 | 80 |
| 85 // Extract new version to a final destination. | 81 // Extract new version to a final destination. |
| 86 newFiles := makeStringSet() | 82 newFiles := makeStringSet() |
| 87 destPath, err := deployInstance(root, inst, newFiles) | 83 destPath, err := deployInstance(root, inst, newFiles) |
| 88 if err != nil { | 84 if err != nil { |
| 89 » » return | 85 » » return common.Pin{}, err |
| 90 } | 86 } |
| 91 | 87 |
| 92 // Switch '_current' symlink to point to a new package instance. It is a | 88 // Switch '_current' symlink to point to a new package instance. It is a |
| 93 // point of no return. The function must not fail going forward. | 89 // point of no return. The function must not fail going forward. |
| 94 » mainSymlinkPath := packagePath(root, inst.PackageName(), currentSymlink) | 90 » mainSymlinkPath := packagePath(root, pin.PackageName, currentSymlink) |
| 95 » err = ensureSymlink(mainSymlinkPath, inst.InstanceID()) | 91 » err = ensureSymlink(mainSymlinkPath, pin.InstanceID) |
| 96 if err != nil { | 92 if err != nil { |
| 97 ensureDirectoryGone(destPath) | 93 ensureDirectoryGone(destPath) |
| 98 » » return | 94 » » return common.Pin{}, err |
| 99 } | 95 } |
| 100 | 96 |
| 101 // Asynchronously remove previous version (best effort). | 97 // Asynchronously remove previous version (best effort). |
| 102 wg := sync.WaitGroup{} | 98 wg := sync.WaitGroup{} |
| 103 defer wg.Wait() | 99 defer wg.Wait() |
| 104 » if prevID != "" && prevID != inst.InstanceID() { | 100 » if prevID != "" && prevID != pin.InstanceID { |
| 105 wg.Add(1) | 101 wg.Add(1) |
| 106 go func() { | 102 go func() { |
| 107 defer wg.Done() | 103 defer wg.Done() |
| 108 » » » ensureDirectoryGone(packagePath(root, inst.PackageName() , prevID)) | 104 » » » ensureDirectoryGone(packagePath(root, pin.PackageName, p revID)) |
| 109 }() | 105 }() |
| 110 } | 106 } |
| 111 | 107 |
| 112 » log.Infof("Adjusting symlinks for %s", inst.PackageName()) | 108 » log.Infof("Adjusting symlinks for %s", pin.PackageName) |
| 113 | 109 |
| 114 // Make symlinks in the site directory for all new files. Reference a pa ckage | 110 // Make symlinks in the site directory for all new files. Reference a pa ckage |
| 115 // root via '_current' symlink (instead of direct destPath), to make | 111 // root via '_current' symlink (instead of direct destPath), to make |
| 116 // subsequent updates 'more atomic' (since they'll need to switch only | 112 // subsequent updates 'more atomic' (since they'll need to switch only |
| 117 // '_current' symlink to update _all_ files in the site root at once). | 113 // '_current' symlink to update _all_ files in the site root at once). |
| 118 linkFilesToRoot(root, mainSymlinkPath, newFiles) | 114 linkFilesToRoot(root, mainSymlinkPath, newFiles) |
| 119 | 115 |
| 120 // Delete symlinks to files no longer needed i.e. set(old) - set(new). | 116 // Delete symlinks to files no longer needed i.e. set(old) - set(new). |
| 121 for relPath := range oldFiles.diff(newFiles) { | 117 for relPath := range oldFiles.diff(newFiles) { |
| 122 ensureFileGone(filepath.Join(root, relPath)) | 118 ensureFileGone(filepath.Join(root, relPath)) |
| 123 } | 119 } |
| 124 | 120 |
| 125 // Verify it's all right, read the manifest. | 121 // Verify it's all right, read the manifest. |
| 126 » state, err = CheckDeployed(root, inst.PackageName()) | 122 » newPin, err := CheckDeployed(root, pin.PackageName) |
| 127 » if err == nil && state.InstanceID != inst.InstanceID() { | 123 » if err == nil && newPin.InstanceID != pin.InstanceID { |
| 128 » » err = fmt.Errorf("Other package instance (%s) was deployed concu rrently", state.InstanceID) | 124 » » err = fmt.Errorf("Other instance (%s) was deployed concurrently" , newPin.InstanceID) |
| 129 } | 125 } |
| 130 if err == nil { | 126 if err == nil { |
| 131 » » log.Infof("Successfully deployed %s:%s", inst.PackageName(), ins t.InstanceID()) | 127 » » log.Infof("Successfully deployed %s", pin) |
| 132 } else { | 128 } else { |
| 133 » » log.Errorf("Failed to deploy %s:%s: %s", inst.PackageName(), ins t.InstanceID(), err.Error()) | 129 » » log.Errorf("Failed to deploy %s: %s", pin, err) |
| 134 } | 130 } |
| 135 » return | 131 » return newPin, err |
| 136 } | 132 } |
| 137 | 133 |
| 138 // CheckDeployed checks whether a given package is deployed and returns | 134 // CheckDeployed checks whether a given package is deployed and returns |
| 139 // information about it if it is. | 135 // information about it if it is. |
| 140 func CheckDeployed(root string, pkg string) (state PackageState, err error) { | 136 func CheckDeployed(root string, pkg string) (common.Pin, error) { |
| 141 » state, err = readPackageState(packagePath(root, pkg)) | 137 » pin, err := readPackageState(packagePath(root, pkg)) |
| 142 » if err != nil { | 138 » if err == nil && pin.PackageName != pkg { |
| 143 » » return | |
| 144 » } | |
| 145 » if state.PackageName != pkg { | |
| 146 err = fmt.Errorf("Package path and package name in the manifest do not match") | 139 err = fmt.Errorf("Package path and package name in the manifest do not match") |
| 147 } | 140 } |
| 148 » return | 141 » return pin, err |
| 149 } | 142 } |
| 150 | 143 |
| 151 // FindDeployed returns a list of packages deployed to a site root. | 144 // FindDeployed returns a list of packages deployed to a site root. |
| 152 func FindDeployed(root string) (out []PackageState, err error) { | 145 func FindDeployed(root string) (out []common.Pin, err error) { |
| 153 root, err = filepath.Abs(filepath.Clean(root)) | 146 root, err = filepath.Abs(filepath.Clean(root)) |
| 154 if err != nil { | 147 if err != nil { |
| 155 return | 148 return |
| 156 } | 149 } |
| 157 | 150 |
| 158 // Directories with packages are direct children of .cipd/pkgs/. | 151 // Directories with packages are direct children of .cipd/pkgs/. |
| 159 pkgs := filepath.Join(root, filepath.FromSlash(packagesDir)) | 152 pkgs := filepath.Join(root, filepath.FromSlash(packagesDir)) |
| 160 infos, err := ioutil.ReadDir(pkgs) | 153 infos, err := ioutil.ReadDir(pkgs) |
| 161 if err != nil { | 154 if err != nil { |
| 162 if os.IsNotExist(err) { | 155 if os.IsNotExist(err) { |
| 163 err = nil | 156 err = nil |
| 164 return | 157 return |
| 165 } | 158 } |
| 166 return | 159 return |
| 167 } | 160 } |
| 168 | 161 |
| 169 // Read the package name from the package manifest. Skip broken stuff. | 162 // Read the package name from the package manifest. Skip broken stuff. |
| 170 » found := map[string]PackageState{} | 163 » found := map[string]common.Pin{} |
| 171 keys := []string{} | 164 keys := []string{} |
| 172 for _, info := range infos { | 165 for _, info := range infos { |
| 173 // Attempt to read the manifest. If it is there -> valid package is found. | 166 // Attempt to read the manifest. If it is there -> valid package is found. |
| 174 if info.IsDir() { | 167 if info.IsDir() { |
| 175 » » » state, err := readPackageState(filepath.Join(pkgs, info. Name())) | 168 » » » pin, err := readPackageState(filepath.Join(pkgs, info.Na me())) |
| 176 if err == nil { | 169 if err == nil { |
| 177 // Ignore duplicate entries, they can appear if someone messes with | 170 // Ignore duplicate entries, they can appear if someone messes with |
| 178 // pkgs/* structure manually. | 171 // pkgs/* structure manually. |
| 179 » » » » if _, ok := found[state.PackageName]; !ok { | 172 » » » » if _, ok := found[pin.PackageName]; !ok { |
| 180 » » » » » keys = append(keys, state.PackageName) | 173 » » » » » keys = append(keys, pin.PackageName) |
| 181 » » » » » found[state.PackageName] = state | 174 » » » » » found[pin.PackageName] = pin |
| 182 } | 175 } |
| 183 } | 176 } |
| 184 } | 177 } |
| 185 } | 178 } |
| 186 | 179 |
| 187 // Sort by package name. | 180 // Sort by package name. |
| 188 sort.Strings(keys) | 181 sort.Strings(keys) |
| 189 » out = make([]PackageState, len(found)) | 182 » out = make([]common.Pin, len(found)) |
| 190 for i, k := range keys { | 183 for i, k := range keys { |
| 191 out[i] = found[k] | 184 out[i] = found[k] |
| 192 } | 185 } |
| 193 return | 186 return |
| 194 } | 187 } |
| 195 | 188 |
| 196 // RemoveDeployed deletes a package given its name. | 189 // RemoveDeployed deletes a package given its name. |
| 197 func RemoveDeployed(root string, packageName string) error { | 190 func RemoveDeployed(root string, packageName string) error { |
| 198 root, err := filepath.Abs(filepath.Clean(root)) | 191 root, err := filepath.Abs(filepath.Clean(root)) |
| 199 if err != nil { | 192 if err != nil { |
| 200 return err | 193 return err |
| 201 } | 194 } |
| 202 log.Infof("Removing %s from %s", packageName, root) | 195 log.Infof("Removing %s from %s", packageName, root) |
| 203 | 196 |
| 204 // Be paranoid. | 197 // Be paranoid. |
| 205 » err = ValidatePackageName(packageName) | 198 » err = common.ValidatePackageName(packageName) |
| 206 if err != nil { | 199 if err != nil { |
| 207 return err | 200 return err |
| 208 } | 201 } |
| 209 | 202 |
| 210 // Grab list of files in currently deployed package to unlink them from root. | 203 // Grab list of files in currently deployed package to unlink them from root. |
| 211 files := makeStringSet() | 204 files := makeStringSet() |
| 212 instanceID := findDeployedInstance(root, packageName, files) | 205 instanceID := findDeployedInstance(root, packageName, files) |
| 213 | 206 |
| 214 // If was installed, removed symlinks pointing to the package files. | 207 // If was installed, removed symlinks pointing to the package files. |
| 215 if instanceID != "" { | 208 if instanceID != "" { |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 237 return state.InstanceID | 230 return state.InstanceID |
| 238 } | 231 } |
| 239 | 232 |
| 240 // deployInstance atomically extracts a package instance to its final | 233 // deployInstance atomically extracts a package instance to its final |
| 241 // destination and returns a path to it. It writes a list of extracted files | 234 // destination and returns a path to it. It writes a list of extracted files |
| 242 // to 'files'. File paths in 'files' are relative to package root. | 235 // to 'files'. File paths in 'files' are relative to package root. |
| 243 func deployInstance(root string, inst PackageInstance, files stringSet) (string, error) { | 236 func deployInstance(root string, inst PackageInstance, files stringSet) (string, error) { |
| 244 // Extract new version to a final destination. ExtractPackageInstance kn ows | 237 // Extract new version to a final destination. ExtractPackageInstance kn ows |
| 245 // how to build full paths and how to atomically extract a package. No n eed | 238 // how to build full paths and how to atomically extract a package. No n eed |
| 246 // to delete garbage if it fails. | 239 // to delete garbage if it fails. |
| 247 » destPath := packagePath(root, inst.PackageName(), inst.InstanceID()) | 240 » destPath := packagePath(root, inst.Pin().PackageName, inst.Pin().Instanc eID) |
| 248 err := ExtractInstance(inst, NewFileSystemDestination(destPath)) | 241 err := ExtractInstance(inst, NewFileSystemDestination(destPath)) |
| 249 if err != nil { | 242 if err != nil { |
| 250 return "", err | 243 return "", err |
| 251 } | 244 } |
| 252 // Enumerate files inside. Nuke it and fail if it's unreadable. | 245 // Enumerate files inside. Nuke it and fail if it's unreadable. |
| 253 » err = scanPackageDir(packagePath(root, inst.PackageName(), inst.Instance ID()), files) | 246 » err = scanPackageDir(packagePath(root, inst.Pin().PackageName, inst.Pin( ).InstanceID), files) |
| 254 if err != nil { | 247 if err != nil { |
| 255 ensureDirectoryGone(destPath) | 248 ensureDirectoryGone(destPath) |
| 256 return "", err | 249 return "", err |
| 257 } | 250 } |
| 258 return destPath, err | 251 return destPath, err |
| 259 } | 252 } |
| 260 | 253 |
| 261 // linkFilesToRoot makes symlinks in root that point to files in packageRoot. | 254 // linkFilesToRoot makes symlinks in root that point to files in packageRoot. |
| 262 // All targets are specified by 'files' as paths relative to packageRoot. This | 255 // All targets are specified by 'files' as paths relative to packageRoot. This |
| 263 // function is best effort and thus doesn't return errors. | 256 // function is best effort and thus doesn't return errors. |
| 264 func linkFilesToRoot(root string, packageRoot string, files stringSet) { | 257 func linkFilesToRoot(root string, packageRoot string, files stringSet) { |
| 265 for relPath := range files { | 258 for relPath := range files { |
| 266 // E.g <root>/bin/tool. | 259 // E.g <root>/bin/tool. |
| 267 symlinkAbs := filepath.Join(root, relPath) | 260 symlinkAbs := filepath.Join(root, relPath) |
| 268 // E.g. <root>/.cipd/pkgs/name/_current/bin/tool. | 261 // E.g. <root>/.cipd/pkgs/name/_current/bin/tool. |
| 269 targetAbs := filepath.Join(packageRoot, relPath) | 262 targetAbs := filepath.Join(packageRoot, relPath) |
| 270 // E.g. ../.cipd/pkgs/name/_current/bin/tool. | 263 // E.g. ../.cipd/pkgs/name/_current/bin/tool. |
| 271 targetRel, err := filepath.Rel(filepath.Dir(symlinkAbs), targetA bs) | 264 targetRel, err := filepath.Rel(filepath.Dir(symlinkAbs), targetA bs) |
| 272 if err != nil { | 265 if err != nil { |
| 273 » » » log.Warnf("Can't get relative path from %s to %s", filep ath.Dir(symlinkAbs), targetAbs) | 266 » » » log.Warningf("Can't get relative path from %s to %s", fi lepath.Dir(symlinkAbs), targetAbs) |
| 274 continue | 267 continue |
| 275 } | 268 } |
| 276 err = ensureSymlink(symlinkAbs, targetRel) | 269 err = ensureSymlink(symlinkAbs, targetRel) |
| 277 if err != nil { | 270 if err != nil { |
| 278 » » » log.Warnf("Failed to create symlink for %s", relPath) | 271 » » » log.Warningf("Failed to create symlink for %s", relPath) |
| 279 continue | 272 continue |
| 280 } | 273 } |
| 281 } | 274 } |
| 282 } | 275 } |
| 283 | 276 |
| 284 // packagePath joins paths together to return absolute path to .cipd/pkgs sub pa th. | 277 // packagePath joins paths together to return absolute path to .cipd/pkgs sub pa th. |
| 285 func packagePath(root string, pkg string, rest ...string) string { | 278 func packagePath(root string, pkg string, rest ...string) string { |
| 286 root, err := filepath.Abs(filepath.Clean(root)) | 279 root, err := filepath.Abs(filepath.Clean(root)) |
| 287 if err != nil { | 280 if err != nil { |
| 288 panic(fmt.Sprintf("Can't get absolute path of '%s'", root)) | 281 panic(fmt.Sprintf("Can't get absolute path of '%s'", root)) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 300 } | 293 } |
| 301 return result | 294 return result |
| 302 } | 295 } |
| 303 | 296 |
| 304 // packageNameDigest returns a filename to use for naming a package directory in | 297 // packageNameDigest returns a filename to use for naming a package directory in |
| 305 // the file system. Using package names as is can introduce problems on file | 298 // the file system. Using package names as is can introduce problems on file |
| 306 // systems with path length limits (on Windows in particular). Returns last two | 299 // systems with path length limits (on Windows in particular). Returns last two |
| 307 // components of the package name + stripped SHA1 of the whole package name. | 300 // components of the package name + stripped SHA1 of the whole package name. |
| 308 func packageNameDigest(pkg string) string { | 301 func packageNameDigest(pkg string) string { |
| 309 // Be paranoid. | 302 // Be paranoid. |
| 310 » err := ValidatePackageName(pkg) | 303 » err := common.ValidatePackageName(pkg) |
| 311 if err != nil { | 304 if err != nil { |
| 312 panic(err.Error()) | 305 panic(err.Error()) |
| 313 } | 306 } |
| 314 | 307 |
| 315 // Grab stripped SHA1 of the full package name. | 308 // Grab stripped SHA1 of the full package name. |
| 316 h := sha1.New() | 309 h := sha1.New() |
| 317 h.Write([]byte(pkg)) | 310 h.Write([]byte(pkg)) |
| 318 hash := base64.URLEncoding.EncodeToString(h.Sum(nil))[:10] | 311 hash := base64.URLEncoding.EncodeToString(h.Sum(nil))[:10] |
| 319 | 312 |
| 320 // Grab last <= 2 components of the package path. | 313 // Grab last <= 2 components of the package path. |
| 321 chunks := strings.Split(pkg, "/") | 314 chunks := strings.Split(pkg, "/") |
| 322 if len(chunks) > 2 { | 315 if len(chunks) > 2 { |
| 323 chunks = chunks[len(chunks)-2:] | 316 chunks = chunks[len(chunks)-2:] |
| 324 } | 317 } |
| 325 | 318 |
| 326 // Join together with '_' as separator. | 319 // Join together with '_' as separator. |
| 327 chunks = append(chunks, hash) | 320 chunks = append(chunks, hash) |
| 328 return strings.Join(chunks, "_") | 321 return strings.Join(chunks, "_") |
| 329 } | 322 } |
| 330 | 323 |
| 331 // readPackageState reads package manifest of a deployed package instance and | 324 // readPackageState reads package manifest of a deployed package instance and |
| 332 // returns corresponding PackageState object. | 325 // returns corresponding Pin object. |
| 333 func readPackageState(packageDir string) (state PackageState, err error) { | 326 func readPackageState(packageDir string) (common.Pin, error) { |
| 334 // Resolve _current symlink to a concrete instance ID. | 327 // Resolve _current symlink to a concrete instance ID. |
| 335 current, err := os.Readlink(filepath.Join(packageDir, currentSymlink)) | 328 current, err := os.Readlink(filepath.Join(packageDir, currentSymlink)) |
| 336 if err != nil { | 329 if err != nil { |
| 337 » » return | 330 » » return common.Pin{}, err |
| 338 } | 331 } |
| 339 » err = ValidateInstanceID(current) | 332 » err = common.ValidateInstanceID(current) |
| 340 if err != nil { | 333 if err != nil { |
| 341 » » err = fmt.Errorf("Symlink target doesn't look like a valid insta nce id") | 334 » » return common.Pin{}, fmt.Errorf("Symlink target doesn't look lik e a valid instance id") |
| 342 » » return | |
| 343 } | 335 } |
| 344 // Read the manifest from the instance directory. | 336 // Read the manifest from the instance directory. |
| 345 manifestPath := filepath.Join(packageDir, current, filepath.FromSlash(ma nifestName)) | 337 manifestPath := filepath.Join(packageDir, current, filepath.FromSlash(ma nifestName)) |
| 346 r, err := os.Open(manifestPath) | 338 r, err := os.Open(manifestPath) |
| 347 if err != nil { | 339 if err != nil { |
| 348 » » return | 340 » » return common.Pin{}, err |
| 349 } | 341 } |
| 350 defer r.Close() | 342 defer r.Close() |
| 351 manifest, err := readManifest(r) | 343 manifest, err := readManifest(r) |
| 352 if err != nil { | 344 if err != nil { |
| 353 » » return | 345 » » return common.Pin{}, err |
| 354 } | 346 } |
| 355 » state = PackageState{ | 347 » return common.Pin{ |
| 356 PackageName: manifest.PackageName, | 348 PackageName: manifest.PackageName, |
| 357 InstanceID: current, | 349 InstanceID: current, |
| 358 » } | 350 » }, nil |
| 359 » return | |
| 360 } | 351 } |
| 361 | 352 |
| 362 // ensureSymlink atomically creates a symlink pointing to a target. It will | 353 // ensureSymlink atomically creates a symlink pointing to a target. It will |
| 363 // create full directory path if necessary. | 354 // create full directory path if necessary. |
| 364 func ensureSymlink(symlink string, target string) error { | 355 func ensureSymlink(symlink string, target string) error { |
| 365 // Already set? | 356 // Already set? |
| 366 existing, err := os.Readlink(symlink) | 357 existing, err := os.Readlink(symlink) |
| 367 if err != nil && existing == target { | 358 if err != nil && existing == target { |
| 368 return nil | 359 return nil |
| 369 } | 360 } |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 398 // to be deployed. | 389 // to be deployed. |
| 399 func scanPackageDir(root string, out stringSet) error { | 390 func scanPackageDir(root string, out stringSet) error { |
| 400 return filepath.Walk(root, func(path string, info os.FileInfo, err error ) error { | 391 return filepath.Walk(root, func(path string, info os.FileInfo, err error ) error { |
| 401 if err != nil { | 392 if err != nil { |
| 402 return err | 393 return err |
| 403 } | 394 } |
| 404 rel, err := filepath.Rel(root, path) | 395 rel, err := filepath.Rel(root, path) |
| 405 if err != nil { | 396 if err != nil { |
| 406 return err | 397 return err |
| 407 } | 398 } |
| 408 » » if rel == packageServiceDir || rel == siteServiceDir { | 399 » » if rel == packageServiceDir || rel == SiteServiceDir { |
| 409 return filepath.SkipDir | 400 return filepath.SkipDir |
| 410 } | 401 } |
| 411 if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { | 402 if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { |
| 412 out.add(rel) | 403 out.add(rel) |
| 413 } | 404 } |
| 414 return nil | 405 return nil |
| 415 }) | 406 }) |
| 416 } | 407 } |
| 417 | 408 |
| 418 // ensureDirectoryGone removes the directory as instantly as possible by | 409 // ensureDirectoryGone removes the directory as instantly as possible by |
| 419 // renaming it first and only then recursively deleting. | 410 // renaming it first and only then recursively deleting. |
| 420 func ensureDirectoryGone(path string) error { | 411 func ensureDirectoryGone(path string) error { |
| 421 temp := fmt.Sprintf("%s_%v", path, time.Now().UnixNano()) | 412 temp := fmt.Sprintf("%s_%v", path, time.Now().UnixNano()) |
| 422 err := os.Rename(path, temp) | 413 err := os.Rename(path, temp) |
| 423 if err != nil { | 414 if err != nil { |
| 424 if !os.IsNotExist(err) { | 415 if !os.IsNotExist(err) { |
| 425 » » » log.Warnf("Failed to rename directory %s: %v", path, err ) | 416 » » » log.Warningf("Failed to rename directory %s: %v", path, err) |
| 426 return err | 417 return err |
| 427 } | 418 } |
| 428 return nil | 419 return nil |
| 429 } | 420 } |
| 430 err = os.RemoveAll(temp) | 421 err = os.RemoveAll(temp) |
| 431 if err != nil { | 422 if err != nil { |
| 432 » » log.Warnf("Failed to remove directory %s: %v", temp, err) | 423 » » log.Warningf("Failed to remove directory %s: %v", temp, err) |
| 433 return err | 424 return err |
| 434 } | 425 } |
| 435 return nil | 426 return nil |
| 436 } | 427 } |
| 437 | 428 |
| 438 // ensureFileGone removes file, logging the errors (if any). | 429 // ensureFileGone removes file, logging the errors (if any). |
| 439 func ensureFileGone(path string) error { | 430 func ensureFileGone(path string) error { |
| 440 err := os.Remove(path) | 431 err := os.Remove(path) |
| 441 if err != nil && !os.IsNotExist(err) { | 432 if err != nil && !os.IsNotExist(err) { |
| 442 » » log.Warnf("Failed to remove %s", path) | 433 » » log.Warningf("Failed to remove %s", path) |
| 443 return err | 434 return err |
| 444 } | 435 } |
| 445 return nil | 436 return nil |
| 446 } | 437 } |
| 447 | 438 |
| 448 //////////////////////////////////////////////////////////////////////////////// | 439 //////////////////////////////////////////////////////////////////////////////// |
| 449 // Simple stringSet implementation for keeping a set of filenames. | 440 // Simple stringSet implementation for keeping a set of filenames. |
| 450 | 441 |
| 451 type stringSet map[string]struct{} | 442 type stringSet map[string]struct{} |
| 452 | 443 |
| 453 func makeStringSet() stringSet { | 444 func makeStringSet() stringSet { |
| 454 return make(stringSet) | 445 return make(stringSet) |
| 455 } | 446 } |
| 456 | 447 |
| 457 // add adds an element to the string set. | 448 // add adds an element to the string set. |
| 458 func (a stringSet) add(elem string) { | 449 func (a stringSet) add(elem string) { |
| 459 a[elem] = struct{}{} | 450 a[elem] = struct{}{} |
| 460 } | 451 } |
| 461 | 452 |
| 462 // diff returns set(a) - set(b). | 453 // diff returns set(a) - set(b). |
| 463 func (a stringSet) diff(b stringSet) stringSet { | 454 func (a stringSet) diff(b stringSet) stringSet { |
| 464 out := makeStringSet() | 455 out := makeStringSet() |
| 465 for elem := range a { | 456 for elem := range a { |
| 466 if _, ok := b[elem]; !ok { | 457 if _, ok := b[elem]; !ok { |
| 467 out.add(elem) | 458 out.add(elem) |
| 468 } | 459 } |
| 469 } | 460 } |
| 470 return out | 461 return out |
| 471 } | 462 } |
| OLD | NEW |