github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/api/applicationbits/application_bits.go (about) 1 package applicationbits 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "mime/multipart" 9 "net/textproto" 10 "os" 11 "time" 12 13 "code.cloudfoundry.org/cli/cf/api/resources" 14 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 15 . "code.cloudfoundry.org/cli/cf/i18n" 16 "code.cloudfoundry.org/cli/cf/net" 17 "code.cloudfoundry.org/gofileutils/fileutils" 18 ) 19 20 const ( 21 DefaultAppUploadBitsTimeout = 15 * time.Minute 22 ) 23 24 //go:generate counterfeiter . Repository 25 26 type Repository interface { 27 GetApplicationFiles(appFilesRequest []resources.AppFileResource) ([]resources.AppFileResource, error) 28 UploadBits(appGUID string, zipFile *os.File, presentFiles []resources.AppFileResource) (apiErr error) 29 } 30 31 type CloudControllerApplicationBitsRepository struct { 32 config coreconfig.Reader 33 gateway net.Gateway 34 } 35 36 func NewCloudControllerApplicationBitsRepository(config coreconfig.Reader, gateway net.Gateway) (repo CloudControllerApplicationBitsRepository) { 37 repo.config = config 38 repo.gateway = gateway 39 return 40 } 41 42 func (repo CloudControllerApplicationBitsRepository) UploadBits(appGUID string, zipFile *os.File, presentFiles []resources.AppFileResource) (apiErr error) { 43 apiURL := fmt.Sprintf("/v2/apps/%s/bits", appGUID) 44 fileutils.TempFile("requests", func(requestFile *os.File, err error) { 45 if err != nil { 46 apiErr = fmt.Errorf("%s: %s", T("Error creating tmp file: {{.Err}}", map[string]interface{}{"Err": err}), err.Error()) 47 return 48 } 49 50 // json.Marshal represents a nil value as "null" instead of an empty slice "[]" 51 if presentFiles == nil { 52 presentFiles = []resources.AppFileResource{} 53 } 54 55 presentFilesJSON, err := json.Marshal(presentFiles) 56 if err != nil { 57 apiErr = fmt.Errorf("%s: %s", T("Error marshaling JSON"), err.Error()) 58 return 59 } 60 61 boundary, err := repo.writeUploadBody(zipFile, requestFile, presentFilesJSON) 62 if err != nil { 63 apiErr = fmt.Errorf("%s: %s", T("Error writing to tmp file: {{.Err}}", map[string]interface{}{"Err": err}), err.Error()) 64 return 65 } 66 67 var request *net.Request 68 request, apiErr = repo.gateway.NewRequestForFile("PUT", repo.config.APIEndpoint()+apiURL, repo.config.AccessToken(), requestFile) 69 if apiErr != nil { 70 return 71 } 72 73 contentType := fmt.Sprintf("multipart/form-data; boundary=%s", boundary) 74 request.HTTPReq.Header.Set("Content-Type", contentType) 75 76 response := &resources.Resource{} 77 _, apiErr = repo.gateway.PerformPollingRequestForJSONResponse(repo.config.APIEndpoint(), request, response, DefaultAppUploadBitsTimeout) 78 if apiErr != nil { 79 return 80 } 81 }) 82 83 return 84 } 85 86 func (repo CloudControllerApplicationBitsRepository) GetApplicationFiles(appFilesToCheck []resources.AppFileResource) ([]resources.AppFileResource, error) { 87 integrityFieldsJSON, err := json.Marshal(mapAppFilesToIntegrityFields(appFilesToCheck)) 88 if err != nil { 89 apiErr := fmt.Errorf("%s: %s", T("Failed to create json for resource_match request"), err.Error()) 90 return nil, apiErr 91 } 92 93 responseFieldsColl := []resources.IntegrityFields{} 94 apiErr := repo.gateway.UpdateResourceSync( 95 repo.config.APIEndpoint(), 96 "/v2/resource_match", 97 bytes.NewReader(integrityFieldsJSON), 98 &responseFieldsColl) 99 100 if apiErr != nil { 101 return nil, apiErr 102 } 103 104 return intersectAppFilesIntegrityFields(appFilesToCheck, responseFieldsColl), nil 105 } 106 107 func mapAppFilesToIntegrityFields(in []resources.AppFileResource) (out []resources.IntegrityFields) { 108 for _, appFile := range in { 109 out = append(out, appFile.ToIntegrityFields()) 110 } 111 return out 112 } 113 114 func intersectAppFilesIntegrityFields( 115 appFiles []resources.AppFileResource, 116 integrityFields []resources.IntegrityFields, 117 ) (out []resources.AppFileResource) { 118 inputFiles := appFilesBySha(appFiles) 119 for _, responseFields := range integrityFields { 120 item, found := inputFiles[responseFields.Sha1] 121 if found { 122 out = append(out, item) 123 } 124 } 125 return out 126 } 127 128 func appFilesBySha(in []resources.AppFileResource) (out map[string]resources.AppFileResource) { 129 out = map[string]resources.AppFileResource{} 130 for _, inputFileResource := range in { 131 out[inputFileResource.Sha1] = inputFileResource 132 } 133 return out 134 } 135 136 func (repo CloudControllerApplicationBitsRepository) writeUploadBody(zipFile *os.File, body *os.File, presentResourcesJSON []byte) (boundary string, err error) { 137 writer := multipart.NewWriter(body) 138 defer writer.Close() 139 140 boundary = writer.Boundary() 141 142 part, err := writer.CreateFormField("resources") 143 if err != nil { 144 return 145 } 146 147 _, err = io.Copy(part, bytes.NewBuffer(presentResourcesJSON)) 148 if err != nil { 149 return 150 } 151 152 if zipFile != nil { 153 zipStats, zipErr := zipFile.Stat() 154 if zipErr != nil { 155 return 156 } 157 158 if zipStats.Size() == 0 { 159 return 160 } 161 162 part, zipErr = createZipPartWriter(zipStats, writer) 163 if zipErr != nil { 164 return 165 } 166 167 _, zipErr = io.Copy(part, zipFile) 168 if zipErr != nil { 169 return 170 } 171 } 172 173 return 174 } 175 176 func createZipPartWriter(zipStats os.FileInfo, writer *multipart.Writer) (io.Writer, error) { 177 h := make(textproto.MIMEHeader) 178 h.Set("Content-Disposition", `form-data; name="application"; filename="application.zip"`) 179 h.Set("Content-Type", "application/zip") 180 h.Set("Content-Length", fmt.Sprintf("%d", zipStats.Size())) 181 h.Set("Content-Transfer-Encoding", "binary") 182 return writer.CreatePart(h) 183 }