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