github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/package.go (about) 1 package ccv3 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "mime/multipart" 8 "os" 9 "path/filepath" 10 11 "code.cloudfoundry.org/cli/api/cloudcontroller" 12 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 13 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 14 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal" 15 "code.cloudfoundry.org/cli/resources" 16 ) 17 18 //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 io.Reader 19 20 // CreatePackage creates a package with the given settings, Type and the 21 // ApplicationRelationship must be set. 22 func (client *Client) CreatePackage(pkg resources.Package) (resources.Package, Warnings, error) { 23 var responseBody resources.Package 24 25 _, warnings, err := client.MakeRequest(RequestParams{ 26 RequestName: internal.PostPackageRequest, 27 RequestBody: pkg, 28 ResponseBody: &responseBody, 29 }) 30 31 return responseBody, warnings, err 32 } 33 34 // GetPackage returns the package with the given GUID. 35 func (client *Client) GetPackage(packageGUID string) (resources.Package, Warnings, error) { 36 var responseBody resources.Package 37 38 _, warnings, err := client.MakeRequest(RequestParams{ 39 RequestName: internal.GetPackageRequest, 40 URIParams: internal.Params{"package_guid": packageGUID}, 41 ResponseBody: &responseBody, 42 }) 43 44 return responseBody, warnings, err 45 } 46 47 // GetPackages returns the list of packages. 48 func (client *Client) GetPackages(query ...Query) ([]resources.Package, Warnings, error) { 49 var packages []resources.Package 50 51 _, warnings, err := client.MakeListRequest(RequestParams{ 52 RequestName: internal.GetPackagesRequest, 53 Query: query, 54 ResponseBody: resources.Package{}, 55 AppendToList: func(item interface{}) error { 56 packages = append(packages, item.(resources.Package)) 57 return nil 58 }, 59 }) 60 61 return packages, warnings, err 62 } 63 64 // UploadBitsPackage uploads the newResources and a list of existing resources 65 // to the cloud controller. An updated package is returned. The function will 66 // act differently given the following Readers: 67 // - io.ReadSeeker: Will function properly on retry. 68 // - io.Reader: Will return a ccerror.PipeSeekError on retry. 69 // - nil: Will not add the "application" section to the request. The newResourcesLength is ignored in this case. 70 // 71 // Note: In order to determine if package creation is successful, poll the 72 // Package's state field for more information. 73 func (client *Client) UploadBitsPackage(pkg resources.Package, matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (resources.Package, Warnings, error) { 74 if matchedResources == nil { 75 return resources.Package{}, nil, ccerror.NilObjectError{Object: "matchedResources"} 76 } 77 78 if newResources == nil { 79 return client.uploadExistingResourcesOnly(pkg.GUID, matchedResources) 80 } 81 82 return client.uploadNewAndExistingResources(pkg.GUID, matchedResources, newResources, newResourcesLength) 83 } 84 85 // UploadPackage uploads a file to a given package's Upload resource. Note: 86 // fileToUpload is read entirely into memory prior to sending data to CC. 87 func (client *Client) UploadPackage(pkg resources.Package, fileToUpload string) (resources.Package, Warnings, error) { 88 body, contentType, err := client.createUploadBuffer(fileToUpload, "bits") 89 if err != nil { 90 return resources.Package{}, nil, err 91 } 92 93 responsePackage := resources.Package{} 94 _, warnings, err := client.MakeRequestSendRaw( 95 internal.PostPackageBitsRequest, 96 internal.Params{"package_guid": pkg.GUID}, 97 body.Bytes(), 98 contentType, 99 &responsePackage, 100 ) 101 102 return responsePackage, warnings, err 103 } 104 105 // CopyPackage copies a package from a source package to a destination package 106 // Note: source app guid is in URL; dest app guid is in body 107 func (client *Client) CopyPackage(sourcePkgGUID string, targetAppGUID string) (resources.Package, Warnings, error) { 108 var targetPackage resources.Package 109 110 _, warnings, err := client.MakeRequest(RequestParams{ 111 RequestName: internal.PostPackageRequest, 112 Query: []Query{{Key: SourceGUID, Values: []string{sourcePkgGUID}}}, 113 RequestBody: map[string]resources.Relationships{ 114 "relationships": { 115 constant.RelationshipTypeApplication: resources.Relationship{GUID: targetAppGUID}, 116 }, 117 }, 118 ResponseBody: &targetPackage, 119 }) 120 121 return targetPackage, warnings, err 122 } 123 124 func (client *Client) calculateAppBitsRequestSize(matchedResources []Resource, newResourcesLength int64) (int64, error) { 125 body := &bytes.Buffer{} 126 form := multipart.NewWriter(body) 127 128 jsonResources, err := json.Marshal(matchedResources) 129 if err != nil { 130 return 0, err 131 } 132 err = form.WriteField("resources", string(jsonResources)) 133 if err != nil { 134 return 0, err 135 } 136 _, err = form.CreateFormFile("bits", "package.zip") 137 if err != nil { 138 return 0, err 139 } 140 err = form.Close() 141 if err != nil { 142 return 0, err 143 } 144 145 return int64(body.Len()) + newResourcesLength, nil 146 } 147 148 func (client *Client) createMultipartBodyAndHeaderForAppBits(matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (string, io.ReadSeeker, <-chan error) { 149 writerOutput, writerInput := cloudcontroller.NewPipeBomb() 150 form := multipart.NewWriter(writerInput) 151 152 writeErrors := make(chan error) 153 154 go func() { 155 defer close(writeErrors) 156 defer writerInput.Close() 157 158 jsonResources, err := json.Marshal(matchedResources) 159 if err != nil { 160 writeErrors <- err 161 return 162 } 163 164 err = form.WriteField("resources", string(jsonResources)) 165 if err != nil { 166 writeErrors <- err 167 return 168 } 169 170 writer, err := form.CreateFormFile("bits", "package.zip") 171 if err != nil { 172 writeErrors <- err 173 return 174 } 175 176 if newResourcesLength != 0 { 177 _, err = io.Copy(writer, newResources) 178 if err != nil { 179 writeErrors <- err 180 return 181 } 182 } 183 184 err = form.Close() 185 if err != nil { 186 writeErrors <- err 187 } 188 }() 189 190 return form.FormDataContentType(), writerOutput, writeErrors 191 } 192 193 func (*Client) createUploadBuffer(path string, paramName string) (bytes.Buffer, string, error) { 194 file, err := os.Open(path) 195 if err != nil { 196 return bytes.Buffer{}, "", err 197 } 198 defer file.Close() 199 200 body := bytes.Buffer{} 201 writer := multipart.NewWriter(&body) 202 part, err := writer.CreateFormFile(paramName, filepath.Base(path)) 203 if err != nil { 204 return bytes.Buffer{}, "", err 205 } 206 _, err = io.Copy(part, file) 207 if err != nil { 208 return bytes.Buffer{}, "", err 209 } 210 211 err = writer.Close() 212 213 return body, writer.FormDataContentType(), err 214 } 215 216 func (client *Client) uploadExistingResourcesOnly(packageGUID string, matchedResources []Resource) (resources.Package, Warnings, error) { 217 jsonResources, err := json.Marshal(matchedResources) 218 if err != nil { 219 return resources.Package{}, nil, err 220 } 221 222 body := bytes.NewBuffer(nil) 223 form := multipart.NewWriter(body) 224 err = form.WriteField("resources", string(jsonResources)) 225 if err != nil { 226 return resources.Package{}, nil, err 227 } 228 229 err = form.Close() 230 if err != nil { 231 return resources.Package{}, nil, err 232 } 233 234 responsePackage := resources.Package{} 235 236 _, warnings, err := client.MakeRequestSendRaw( 237 internal.PostPackageBitsRequest, 238 internal.Params{"package_guid": packageGUID}, 239 body.Bytes(), 240 form.FormDataContentType(), 241 &responsePackage, 242 ) 243 244 return responsePackage, warnings, err 245 } 246 247 func (client *Client) uploadNewAndExistingResources(packageGUID string, matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (resources.Package, Warnings, error) { 248 contentLength, err := client.calculateAppBitsRequestSize(matchedResources, newResourcesLength) 249 if err != nil { 250 return resources.Package{}, nil, err 251 } 252 253 contentType, body, writeErrors := client.createMultipartBodyAndHeaderForAppBits(matchedResources, newResources, newResourcesLength) 254 255 responseBody := resources.Package{} 256 _, warnings, err := client.MakeRequestUploadAsync( 257 internal.PostPackageBitsRequest, 258 internal.Params{"package_guid": packageGUID}, 259 contentType, 260 body, 261 contentLength, 262 &responseBody, 263 writeErrors, 264 ) 265 return responseBody, warnings, err 266 }