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