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 the deployed instance. |
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 |