| OLD | NEW |
| 1 // Copyright 2017 The LUCI Authors. | 1 // Copyright 2017 The LUCI Authors. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 package main | 15 package main |
| 16 | 16 |
| 17 import ( | 17 import ( |
| 18 "bytes" |
| 18 "encoding/json" | 19 "encoding/json" |
| 19 "fmt" | 20 "fmt" |
| 20 » "io/ioutil" | 21 » "io" |
| 21 "log" | 22 "log" |
| 22 "os" | 23 "os" |
| 23 "path/filepath" | 24 "path/filepath" |
| 24 "strings" | 25 "strings" |
| 25 | 26 |
| 26 humanize "github.com/dustin/go-humanize" | 27 humanize "github.com/dustin/go-humanize" |
| 27 "github.com/luci/luci-go/common/isolated" | 28 "github.com/luci/luci-go/common/isolated" |
| 28 "github.com/luci/luci-go/common/isolatedclient" | 29 "github.com/luci/luci-go/common/isolatedclient" |
| 29 ) | 30 ) |
| 30 | 31 |
| 32 // limitedOS contains a subset of the functions from the os package. |
| 33 type limitedOS interface { |
| 34 Readlink(string) (string, error) |
| 35 openFiler |
| 36 } |
| 37 |
| 38 type openFiler interface { |
| 39 // OpenFile is like os.OpenFile, but returns an io.WriteCloser since |
| 40 // that's all we need and it's easier to implment with a fake. |
| 41 OpenFile(string, int, os.FileMode) (io.WriteCloser, error) |
| 42 } |
| 43 |
| 44 // standardOS implements limitedOS by delegating to the standard library's os pa
ckage. |
| 45 type standardOS struct{} |
| 46 |
| 47 func (sos standardOS) Readlink(name string) (string, error) { |
| 48 return os.Readlink(name) |
| 49 } |
| 50 |
| 51 func (sos standardOS) OpenFile(name string, flag int, perm os.FileMode) (io.Writ
eCloser, error) { |
| 52 return os.OpenFile(name, flag, perm) |
| 53 } |
| 54 |
| 31 // UploadTracker uploads and keeps track of files. | 55 // UploadTracker uploads and keeps track of files. |
| 32 type UploadTracker struct { | 56 type UploadTracker struct { |
| 33 » checker *Checker | 57 » checker Checker |
| 34 » uploader *Uploader | 58 » uploader Uploader |
| 35 isol *isolated.Isolated | 59 isol *isolated.Isolated |
| 60 |
| 61 // Override for testing. |
| 62 lOS limitedOS |
| 36 } | 63 } |
| 37 | 64 |
| 38 // NewUploadTracker constructs an UploadTracker. It tracks uploaded files in is
ol.Files. | 65 // NewUploadTracker constructs an UploadTracker. It tracks uploaded files in is
ol.Files. |
| 39 func NewUploadTracker(checker *Checker, uploader *Uploader, isol *isolated.Isola
ted) *UploadTracker { | 66 func NewUploadTracker(checker Checker, uploader Uploader, isol *isolated.Isolate
d) *UploadTracker { |
| 40 // TODO: share a Checker and Uploader with other UploadTrackers | 67 // TODO: share a Checker and Uploader with other UploadTrackers |
| 41 // when batch uploading. | 68 // when batch uploading. |
| 42 isol.Files = make(map[string]isolated.File) | 69 isol.Files = make(map[string]isolated.File) |
| 43 return &UploadTracker{ | 70 return &UploadTracker{ |
| 44 checker: checker, | 71 checker: checker, |
| 45 uploader: uploader, | 72 uploader: uploader, |
| 46 isol: isol, | 73 isol: isol, |
| 74 lOS: standardOS{}, |
| 47 } | 75 } |
| 48 } | 76 } |
| 49 | 77 |
| 50 // UploadDeps uploads all of the items in parts. | 78 // UploadDeps uploads all of the items in parts. |
| 51 func (ut *UploadTracker) UploadDeps(parts partitionedDeps) error { | 79 func (ut *UploadTracker) UploadDeps(parts partitionedDeps) error { |
| 52 if err := ut.populateSymlinks(parts.links.items); err != nil { | 80 if err := ut.populateSymlinks(parts.links.items); err != nil { |
| 53 return err | 81 return err |
| 54 } | 82 } |
| 55 | 83 |
| 56 if err := ut.tarAndUploadFiles(parts.filesToArchive.items); err != nil { | 84 if err := ut.tarAndUploadFiles(parts.filesToArchive.items); err != nil { |
| 57 return err | 85 return err |
| 58 } | 86 } |
| 59 | 87 |
| 60 if err := ut.uploadFiles(parts.indivFiles.items); err != nil { | 88 if err := ut.uploadFiles(parts.indivFiles.items); err != nil { |
| 61 return err | 89 return err |
| 62 } | 90 } |
| 63 return nil | 91 return nil |
| 64 } | 92 } |
| 65 | 93 |
| 66 // Files returns the files which have been uploaded. | 94 // Files returns the files which have been uploaded. |
| 67 // Note: files may not have completed uploading until the tracker's Checker and | 95 // Note: files may not have completed uploading until the tracker's Checker and |
| 68 // Uploader have been closed. | 96 // Uploader have been closed. |
| 69 func (ut *UploadTracker) Files() map[string]isolated.File { | 97 func (ut *UploadTracker) Files() map[string]isolated.File { |
| 70 return ut.isol.Files | 98 return ut.isol.Files |
| 71 } | 99 } |
| 72 | 100 |
| 73 // populateSymlinks adds an isolated.File to files for each provided symlink | 101 // populateSymlinks adds an isolated.File to files for each provided symlink |
| 74 func (ut *UploadTracker) populateSymlinks(symlinks []*Item) error { | 102 func (ut *UploadTracker) populateSymlinks(symlinks []*Item) error { |
| 75 for _, item := range symlinks { | 103 for _, item := range symlinks { |
| 76 » » l, err := os.Readlink(item.Path) | 104 » » l, err := ut.lOS.Readlink(item.Path) |
| 77 if err != nil { | 105 if err != nil { |
| 78 return fmt.Errorf("unable to resolve symlink for %q: %v"
, item.Path, err) | 106 return fmt.Errorf("unable to resolve symlink for %q: %v"
, item.Path, err) |
| 79 } | 107 } |
| 80 ut.isol.Files[item.RelPath] = isolated.SymLink(l) | 108 ut.isol.Files[item.RelPath] = isolated.SymLink(l) |
| 81 } | 109 } |
| 82 return nil | 110 return nil |
| 83 } | 111 } |
| 84 | 112 |
| 85 // tarAndUploadFiles creates bundles of files, uploads them, and adds each bundl
e to files. | 113 // tarAndUploadFiles creates bundles of files, uploads them, and adds each bundl
e to files. |
| 86 func (ut *UploadTracker) tarAndUploadFiles(smallFiles []*Item) error { | 114 func (ut *UploadTracker) tarAndUploadFiles(smallFiles []*Item) error { |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 173 if err := ut.checker.Close(); err != nil { | 201 if err := ut.checker.Close(); err != nil { |
| 174 return IsolatedSummary{}, err | 202 return IsolatedSummary{}, err |
| 175 } | 203 } |
| 176 | 204 |
| 177 // Make sure that all the uploads have completed successfully. | 205 // Make sure that all the uploads have completed successfully. |
| 178 if err := ut.uploader.Close(); err != nil { | 206 if err := ut.uploader.Close(); err != nil { |
| 179 return IsolatedSummary{}, err | 207 return IsolatedSummary{}, err |
| 180 } | 208 } |
| 181 | 209 |
| 182 // Write the isolated file... | 210 // Write the isolated file... |
| 183 » if err := isolFile.writeJSONFile(); err != nil { | 211 » if err := isolFile.writeJSONFile(ut.lOS); err != nil { |
| 184 return IsolatedSummary{}, err | 212 return IsolatedSummary{}, err |
| 185 } | 213 } |
| 186 | 214 |
| 187 return IsolatedSummary{ | 215 return IsolatedSummary{ |
| 188 Name: isolFile.name(), | 216 Name: isolFile.name(), |
| 189 Digest: isolFile.item().Digest, | 217 Digest: isolFile.item().Digest, |
| 190 }, nil | 218 }, nil |
| 191 } | 219 } |
| 192 | 220 |
| 193 // isolatedFile is an isolated file which is stored in memory. | 221 // isolatedFile is an isolated file which is stored in memory. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 214 Digest: isolated.HashBytes(ij.json), | 242 Digest: isolated.HashBytes(ij.json), |
| 215 Size: int64(len(ij.json)), | 243 Size: int64(len(ij.json)), |
| 216 } | 244 } |
| 217 } | 245 } |
| 218 | 246 |
| 219 func (ij *isolatedFile) contents() []byte { | 247 func (ij *isolatedFile) contents() []byte { |
| 220 return ij.json | 248 return ij.json |
| 221 } | 249 } |
| 222 | 250 |
| 223 // writeJSONFile writes the file contents to the filesystem. | 251 // writeJSONFile writes the file contents to the filesystem. |
| 224 func (ij *isolatedFile) writeJSONFile() error { | 252 func (ij *isolatedFile) writeJSONFile(opener openFiler) error { |
| 225 » return ioutil.WriteFile(ij.path, ij.json, 0644) | 253 » f, err := opener.OpenFile(ij.path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 064
4) |
| 254 » if err != nil { |
| 255 » » return err |
| 256 » } |
| 257 » _, err = io.Copy(f, bytes.NewBuffer(ij.json)) |
| 258 » err2 := f.Close() |
| 259 » if err != nil { |
| 260 » » return err |
| 261 » } |
| 262 » return err2 |
| 226 } | 263 } |
| 227 | 264 |
| 228 // name returns the base name of the isolated file, extension stripped. | 265 // name returns the base name of the isolated file, extension stripped. |
| 229 func (ij *isolatedFile) name() string { | 266 func (ij *isolatedFile) name() string { |
| 230 name := filepath.Base(ij.path) | 267 name := filepath.Base(ij.path) |
| 231 if i := strings.LastIndex(name, "."); i != -1 { | 268 if i := strings.LastIndex(name, "."); i != -1 { |
| 232 name = name[:i] | 269 name = name[:i] |
| 233 } | 270 } |
| 234 return name | 271 return name |
| 235 } | 272 } |
| OLD | NEW |