github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/cf/api/buildpack_bits.go (about) 1 package api 2 3 import ( 4 "archive/zip" 5 "crypto/tls" 6 "crypto/x509" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "mime/multipart" 11 gonet "net" 12 "net/http" 13 "os" 14 "path" 15 "path/filepath" 16 "strings" 17 "time" 18 19 "code.cloudfoundry.org/cli/cf/appfiles" 20 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 21 "code.cloudfoundry.org/cli/cf/errors" 22 . "code.cloudfoundry.org/cli/cf/i18n" 23 "code.cloudfoundry.org/cli/cf/models" 24 "code.cloudfoundry.org/cli/cf/net" 25 "code.cloudfoundry.org/gofileutils/fileutils" 26 ) 27 28 //go:generate counterfeiter . BuildpackBitsRepository 29 30 type BuildpackBitsRepository interface { 31 UploadBuildpack(buildpack models.Buildpack, buildpackFile *os.File, zipFileName string) error 32 CreateBuildpackZipFile(buildpackPath string) (*os.File, string, error) 33 } 34 35 type CloudControllerBuildpackBitsRepository struct { 36 config coreconfig.Reader 37 gateway net.Gateway 38 zipper appfiles.Zipper 39 TrustedCerts []tls.Certificate 40 } 41 42 func NewCloudControllerBuildpackBitsRepository(config coreconfig.Reader, gateway net.Gateway, zipper appfiles.Zipper) (repo CloudControllerBuildpackBitsRepository) { 43 repo.config = config 44 repo.gateway = gateway 45 repo.zipper = zipper 46 return 47 } 48 49 func zipErrorHelper(err error) error { 50 return fmt.Errorf("%s: %s", T("Couldn't write zip file"), err.Error()) 51 } 52 53 func (repo CloudControllerBuildpackBitsRepository) CreateBuildpackZipFile(buildpackPath string) (*os.File, string, error) { 54 zipFileToUpload, err := ioutil.TempFile("", "buildpack-upload") 55 if err != nil { 56 return nil, "", fmt.Errorf("%s: %s", T("Couldn't create temp file for upload"), err.Error()) 57 } 58 59 var success bool 60 defer func() { 61 if !success { 62 os.RemoveAll(zipFileToUpload.Name()) 63 } 64 }() 65 66 var buildpackFileName string 67 if isWebURL(buildpackPath) { 68 buildpackFileName = path.Base(buildpackPath) 69 repo.downloadBuildpack(buildpackPath, func(downloadFile *os.File, downloadErr error) { 70 if downloadErr != nil { 71 err = downloadErr 72 return 73 } 74 75 downloadErr = normalizeBuildpackArchive(downloadFile, zipFileToUpload) 76 if downloadErr != nil { 77 err = downloadErr 78 return 79 } 80 }) 81 if err != nil { 82 return nil, "", zipErrorHelper(err) 83 } 84 } else { 85 buildpackFileName = filepath.Base(buildpackPath) 86 dir, err := filepath.Abs(buildpackPath) 87 if err != nil { 88 return nil, "", zipErrorHelper(err) 89 } 90 91 buildpackFileName = filepath.Base(dir) 92 stats, err := os.Stat(dir) 93 if err != nil { 94 return nil, "", fmt.Errorf("%s: %s", T("Error opening buildpack file"), err.Error()) 95 } 96 97 if stats.IsDir() { 98 buildpackFileName += ".zip" // FIXME: remove once #71167394 is fixed 99 err = repo.zipper.Zip(buildpackPath, zipFileToUpload) 100 if err != nil { 101 return nil, "", zipErrorHelper(err) 102 } 103 } else { 104 specifiedFile, err := os.Open(buildpackPath) 105 if err != nil { 106 return nil, "", fmt.Errorf("%s: %s", T("Couldn't open buildpack file"), err.Error()) 107 } 108 err = normalizeBuildpackArchive(specifiedFile, zipFileToUpload) 109 if err != nil { 110 return nil, "", zipErrorHelper(err) 111 } 112 } 113 } 114 115 success = true 116 return zipFileToUpload, buildpackFileName, nil 117 } 118 119 func normalizeBuildpackArchive(inputFile *os.File, outputFile *os.File) error { 120 stats, toplevelErr := inputFile.Stat() 121 if toplevelErr != nil { 122 return toplevelErr 123 } 124 125 reader, toplevelErr := zip.NewReader(inputFile, stats.Size()) 126 if toplevelErr != nil { 127 return toplevelErr 128 } 129 130 contents := reader.File 131 132 parentPath, hasBuildpack := findBuildpackPath(contents) 133 134 if !hasBuildpack { 135 return errors.New(T("Zip archive does not contain a buildpack")) 136 } 137 138 writer := zip.NewWriter(outputFile) 139 140 for _, file := range contents { 141 name := file.Name 142 if strings.HasPrefix(name, parentPath) { 143 relativeFilename := strings.TrimPrefix(name, parentPath+"/") 144 if relativeFilename == "" { 145 continue 146 } 147 148 fileInfo := file.FileInfo() 149 header, err := zip.FileInfoHeader(fileInfo) 150 if err != nil { 151 return err 152 } 153 header.Name = relativeFilename 154 155 w, err := writer.CreateHeader(header) 156 if err != nil { 157 return err 158 } 159 160 r, err := file.Open() 161 if err != nil { 162 return err 163 } 164 165 _, err = io.Copy(w, r) 166 if err != nil { 167 return err 168 } 169 170 err = r.Close() 171 if err != nil { 172 return err 173 } 174 } 175 } 176 177 toplevelErr = writer.Close() 178 if toplevelErr != nil { 179 return toplevelErr 180 } 181 182 _, toplevelErr = outputFile.Seek(0, 0) 183 if toplevelErr != nil { 184 return toplevelErr 185 } 186 187 return nil 188 } 189 190 func findBuildpackPath(zipFiles []*zip.File) (parentPath string, foundBuildpack bool) { 191 needle := "bin/compile" 192 193 for _, file := range zipFiles { 194 if strings.HasSuffix(file.Name, needle) { 195 foundBuildpack = true 196 parentPath = path.Join(file.Name, "..", "..") 197 if parentPath == "." { 198 parentPath = "" 199 } 200 return 201 } 202 } 203 return 204 } 205 206 func isWebURL(path string) bool { 207 return strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") 208 } 209 210 func (repo CloudControllerBuildpackBitsRepository) downloadBuildpack(url string, cb func(*os.File, error)) { 211 fileutils.TempFile("buildpack-download", func(tempfile *os.File, err error) { 212 if err != nil { 213 cb(nil, err) 214 return 215 } 216 217 var certPool *x509.CertPool 218 if len(repo.TrustedCerts) > 0 { 219 certPool = x509.NewCertPool() 220 for _, tlsCert := range repo.TrustedCerts { 221 cert, _ := x509.ParseCertificate(tlsCert.Certificate[0]) 222 certPool.AddCert(cert) 223 } 224 } 225 226 client := &http.Client{ 227 Transport: &http.Transport{ 228 Dial: (&gonet.Dialer{Timeout: 5 * time.Second}).Dial, 229 TLSClientConfig: &tls.Config{RootCAs: certPool}, 230 Proxy: http.ProxyFromEnvironment, 231 }, 232 } 233 234 response, err := client.Get(url) 235 if err != nil { 236 cb(nil, err) 237 return 238 } 239 defer response.Body.Close() 240 241 _, err = io.Copy(tempfile, response.Body) 242 if err != nil { 243 cb(nil, err) 244 return 245 } 246 247 _, err = tempfile.Seek(0, 0) 248 if err != nil { 249 cb(nil, err) 250 return 251 } 252 253 cb(tempfile, nil) 254 }) 255 } 256 257 func (repo CloudControllerBuildpackBitsRepository) UploadBuildpack(buildpack models.Buildpack, buildpackFile *os.File, buildpackName string) error { 258 defer func() { 259 buildpackFile.Close() 260 os.Remove(buildpackFile.Name()) 261 }() 262 return repo.performMultiPartUpload( 263 fmt.Sprintf("%s/v2/buildpacks/%s/bits", repo.config.APIEndpoint(), buildpack.GUID), 264 "buildpack", 265 buildpackName, 266 buildpackFile) 267 } 268 269 func (repo CloudControllerBuildpackBitsRepository) performMultiPartUpload(url string, fieldName string, fileName string, body io.Reader) error { 270 var capturedErr error 271 272 fileutils.TempFile("requests", func(requestFile *os.File, err error) { 273 if err != nil { 274 capturedErr = err 275 return 276 } 277 278 writer := multipart.NewWriter(requestFile) 279 part, err := writer.CreateFormFile(fieldName, fileName) 280 281 if err != nil { 282 _ = writer.Close() 283 capturedErr = err 284 return 285 } 286 287 _, err = io.Copy(part, body) 288 if err != nil { 289 capturedErr = fmt.Errorf("%s: %s", T("Error creating upload"), err.Error()) 290 return 291 } 292 293 err = writer.Close() 294 if err != nil { 295 capturedErr = err 296 return 297 } 298 299 var request *net.Request 300 request, err = repo.gateway.NewRequestForFile("PUT", url, repo.config.AccessToken(), requestFile) 301 if err != nil { 302 capturedErr = err 303 return 304 } 305 306 contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()) 307 request.HTTPReq.Header.Set("Content-Type", contentType) 308 309 _, err = repo.gateway.PerformRequest(request) 310 if err != nil { 311 capturedErr = err 312 } 313 }) 314 315 return capturedErr 316 }