github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+incompatible/actor/v2action/resource.go (about) 1 package v2action 2 3 import ( 4 "archive/zip" 5 "crypto/sha1" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2" 14 "code.cloudfoundry.org/ykk" 15 log "github.com/sirupsen/logrus" 16 ) 17 18 const ( 19 DefaultFolderPermissions = 0755 20 DefaultArchiveFilePermissions = 0744 21 ) 22 23 type FileChangedError struct { 24 Filename string 25 } 26 27 func (e FileChangedError) Error() string { 28 return fmt.Sprint("SHA1 mismatch for:", e.Filename) 29 } 30 31 type EmptyDirectoryError struct { 32 Path string 33 } 34 35 func (e EmptyDirectoryError) Error() string { 36 return fmt.Sprint(e.Path, "is empty") 37 } 38 39 type Resource ccv2.Resource 40 41 // GatherArchiveResources returns a list of resources for a directory. 42 func (actor Actor) GatherArchiveResources(archivePath string) ([]Resource, error) { 43 var resources []Resource 44 45 archive, err := os.Open(archivePath) 46 if err != nil { 47 return nil, err 48 } 49 defer archive.Close() 50 51 reader, err := actor.newArchiveReader(archive) 52 if err != nil { 53 return nil, err 54 } 55 56 for _, archivedFile := range reader.File { 57 resource := Resource{Filename: filepath.ToSlash(archivedFile.Name)} 58 if archivedFile.FileInfo().IsDir() { 59 resource.Mode = DefaultFolderPermissions 60 } else { 61 fileReader, err := archivedFile.Open() 62 if err != nil { 63 return nil, err 64 } 65 defer fileReader.Close() 66 67 hash := sha1.New() 68 69 _, err = io.Copy(hash, fileReader) 70 if err != nil { 71 return nil, err 72 } 73 74 resource.Mode = DefaultArchiveFilePermissions 75 resource.SHA1 = fmt.Sprintf("%x", hash.Sum(nil)) 76 resource.Size = archivedFile.FileInfo().Size() 77 } 78 resources = append(resources, resource) 79 } 80 return resources, nil 81 } 82 83 // GatherDirectoryResources returns a list of resources for a directory. 84 func (_ Actor) GatherDirectoryResources(sourceDir string) ([]Resource, error) { 85 var resources []Resource 86 walkErr := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error { 87 if err != nil { 88 return err 89 } 90 91 relPath, err := filepath.Rel(sourceDir, path) 92 if err != nil { 93 return err 94 } 95 96 if relPath == "." { 97 return nil 98 } 99 100 resource := Resource{ 101 Filename: filepath.ToSlash(relPath), 102 } 103 104 if info.IsDir() { 105 resource.Mode = DefaultFolderPermissions 106 } else { 107 file, err := os.Open(path) 108 if err != nil { 109 return err 110 } 111 defer file.Close() 112 113 sum := sha1.New() 114 _, err = io.Copy(sum, file) 115 if err != nil { 116 return err 117 } 118 119 resource.Mode = fixMode(info.Mode()) 120 resource.SHA1 = fmt.Sprintf("%x", sum.Sum(nil)) 121 resource.Size = info.Size() 122 } 123 resources = append(resources, resource) 124 return nil 125 }) 126 127 if len(resources) == 0 { 128 return nil, EmptyDirectoryError{Path: sourceDir} 129 } 130 131 return resources, walkErr 132 } 133 134 // ZipArchiveResources zips an archive and a sorted (based on full 135 // path/filename) list of resources and returns the location. On Windows, the 136 // filemode for user is forced to be readable and executable. 137 func (actor Actor) ZipArchiveResources(sourceArchivePath string, filesToInclude []Resource) (string, error) { 138 log.WithField("sourceArchive", sourceArchivePath).Info("zipping source files from archive") 139 zipFile, err := ioutil.TempFile("", "cf-cli-") 140 if err != nil { 141 return "", err 142 } 143 defer zipFile.Close() 144 145 writer := zip.NewWriter(zipFile) 146 defer writer.Close() 147 148 source, err := os.Open(sourceArchivePath) 149 if err != nil { 150 return "", err 151 } 152 defer source.Close() 153 154 reader, err := actor.newArchiveReader(source) 155 if err != nil { 156 return "", err 157 } 158 159 for _, archiveFile := range reader.File { 160 log.WithField("archiveFileName", archiveFile.Name).Debug("zipping file") 161 162 resource := actor.findInResources(archiveFile.Name, filesToInclude) 163 reader, openErr := archiveFile.Open() 164 if openErr != nil { 165 log.WithField("archiveFile", archiveFile.Name).Errorln("opening path in dir:", openErr) 166 return "", openErr 167 } 168 169 err = actor.addFileToZipFromFileSystem( 170 resource.Filename, reader, archiveFile.FileInfo(), 171 resource.Filename, resource.SHA1, resource.Mode, writer, 172 ) 173 if err != nil { 174 log.WithField("archiveFileName", archiveFile.Name).Errorln("zipping file:", err) 175 return "", err 176 } 177 } 178 179 log.WithFields(log.Fields{ 180 "zip_file_location": zipFile.Name(), 181 "zipped_file_count": len(filesToInclude), 182 }).Info("zip file created") 183 return zipFile.Name(), nil 184 } 185 186 // ZipDirectoryResources zips a directory and a sorted (based on full 187 // path/filename) list of resources and returns the location. On Windows, the 188 // filemode for user is forced to be readable and executable. 189 func (actor Actor) ZipDirectoryResources(sourceDir string, filesToInclude []Resource) (string, error) { 190 log.WithField("sourceDir", sourceDir).Info("zipping source files from directory") 191 zipFile, err := ioutil.TempFile("", "cf-cli-") 192 if err != nil { 193 return "", err 194 } 195 defer zipFile.Close() 196 197 writer := zip.NewWriter(zipFile) 198 defer writer.Close() 199 200 for _, resource := range filesToInclude { 201 fullPath := filepath.Join(sourceDir, resource.Filename) 202 log.WithField("fullPath", fullPath).Debug("zipping file") 203 204 srcFile, err := os.Open(fullPath) 205 if err != nil { 206 log.WithField("fullPath", fullPath).Errorln("opening path in dir:", err) 207 return "", err 208 } 209 210 fileInfo, err := srcFile.Stat() 211 if err != nil { 212 log.WithField("fullPath", fullPath).Errorln("stat error in dir:", err) 213 return "", err 214 } 215 216 err = actor.addFileToZipFromFileSystem( 217 fullPath, srcFile, fileInfo, 218 resource.Filename, resource.SHA1, resource.Mode, writer, 219 ) 220 if err != nil { 221 log.WithField("fullPath", fullPath).Errorln("zipping file:", err) 222 return "", err 223 } 224 } 225 226 log.WithFields(log.Fields{ 227 "zip_file_location": zipFile.Name(), 228 "zipped_file_count": len(filesToInclude), 229 }).Info("zip file created") 230 return zipFile.Name(), nil 231 } 232 233 func (_ Actor) actorToCCResources(resources []Resource) []ccv2.Resource { 234 apiResources := make([]ccv2.Resource, 0, len(resources)) // Explicitly done to prevent nils 235 236 for _, resource := range resources { 237 apiResources = append(apiResources, ccv2.Resource(resource)) 238 } 239 240 return apiResources 241 } 242 243 func (_ Actor) addFileToZipFromFileSystem( 244 srcPath string, srcFile io.ReadCloser, fileInfo os.FileInfo, 245 destPath string, sha1Sum string, mode os.FileMode, zipFile *zip.Writer, 246 ) error { 247 defer srcFile.Close() 248 249 header, err := zip.FileInfoHeader(fileInfo) 250 if err != nil { 251 log.WithField("srcPath", srcPath).Errorln("getting file info in dir:", err) 252 return err 253 } 254 255 // An extra '/' indicates that this file is a directory 256 if fileInfo.IsDir() && !strings.HasSuffix(destPath, "/") { 257 destPath += "/" 258 } 259 260 header.Name = destPath 261 header.Method = zip.Deflate 262 263 header.SetMode(mode) 264 log.WithFields(log.Fields{ 265 "srcPath": srcPath, 266 "destPath": destPath, 267 "mode": mode, 268 }).Debug("setting mode for file") 269 270 destFileWriter, err := zipFile.CreateHeader(header) 271 if err != nil { 272 log.Errorln("creating header:", err) 273 return err 274 } 275 276 if !fileInfo.IsDir() { 277 sum := sha1.New() 278 279 multi := io.MultiWriter(sum, destFileWriter) 280 if _, err := io.Copy(multi, srcFile); err != nil { 281 log.WithField("srcPath", srcPath).Errorln("copying data in dir:", err) 282 return err 283 } 284 285 if currentSum := fmt.Sprintf("%x", sum.Sum(nil)); sha1Sum != currentSum { 286 log.WithFields(log.Fields{ 287 "expected": sha1Sum, 288 "currentSum": currentSum, 289 }).Error("setting mode for file") 290 return FileChangedError{Filename: srcPath} 291 } 292 } 293 294 return nil 295 } 296 297 func (_ Actor) findInResources(path string, filesToInclude []Resource) Resource { 298 for _, resource := range filesToInclude { 299 if resource.Filename == filepath.ToSlash(path) { 300 log.WithField("resource", resource.Filename).Debug("found resource in files to include") 301 return resource 302 } 303 } 304 305 log.WithField("path", path).Debug("did not find resource in files to include") 306 return Resource{} 307 } 308 309 func (_ Actor) newArchiveReader(archive *os.File) (*zip.Reader, error) { 310 info, err := archive.Stat() 311 if err != nil { 312 return nil, err 313 } 314 315 return ykk.NewReader(archive, info.Size()) 316 }