github.com/pivotal-cf/go-pivnet/v6@v6.0.2/product_files.go (about) 1 package pivnet 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 10 "github.com/pivotal-cf/go-pivnet/v6/download" 11 "github.com/pivotal-cf/go-pivnet/v6/logger" 12 ) 13 14 type ProductFilesService struct { 15 client Client 16 } 17 18 type CreateProductFileConfig struct { 19 ProductSlug string 20 AWSObjectKey string 21 Description string 22 DocsURL string 23 FileType string 24 FileVersion string 25 IncludedFiles []string 26 SHA256 string 27 MD5 string 28 Name string 29 Platforms []string 30 ReleasedAt string 31 SystemRequirements []string 32 } 33 34 type ProductFilesResponse struct { 35 ProductFiles []ProductFile `json:"product_files,omitempty"` 36 } 37 38 type ProductFileResponse struct { 39 ProductFile ProductFile `json:"product_file,omitempty"` 40 } 41 42 type ProductFile struct { 43 ID int `json:"id,omitempty" yaml:"id,omitempty"` 44 AWSObjectKey string `json:"aws_object_key,omitempty" yaml:"aws_object_key,omitempty"` 45 Description string `json:"description,omitempty" yaml:"description,omitempty"` 46 DocsURL string `json:"docs_url,omitempty" yaml:"docs_url,omitempty"` 47 FileTransferStatus string `json:"file_transfer_status,omitempty" yaml:"file_transfer_status,omitempty"` 48 FileType string `json:"file_type,omitempty" yaml:"file_type,omitempty"` 49 FileVersion string `json:"file_version,omitempty" yaml:"file_version,omitempty"` 50 HasSignatureFile bool `json:"has_signature_file,omitempty" yaml:"has_signature_file,omitempty"` 51 IncludedFiles []string `json:"included_files,omitempty" yaml:"included_files,omitempty"` 52 SHA256 string `json:"sha256,omitempty" yaml:"sha256,omitempty"` 53 MD5 string `json:"md5,omitempty" yaml:"md5,omitempty"` 54 Name string `json:"name,omitempty" yaml:"name,omitempty"` 55 Platforms []string `json:"platforms,omitempty" yaml:"platforms,omitempty"` 56 ReadyToServe bool `json:"ready_to_serve,omitempty" yaml:"ready_to_serve,omitempty"` 57 ReleasedAt string `json:"released_at,omitempty" yaml:"released_at,omitempty"` 58 Size int `json:"size,omitempty" yaml:"size,omitempty"` 59 SystemRequirements []string `json:"system_requirements,omitempty" yaml:"system_requirements,omitempty"` 60 Links *Links `json:"_links,omitempty" yaml:"_links,omitempty"` 61 } 62 63 func (p ProductFile) DownloadLink() (string, error) { 64 if p.Links == nil { 65 return "", fmt.Errorf("Could not determine download link - links map is empty") 66 } 67 68 return p.Links.Download["href"], nil 69 } 70 71 const ( 72 FileTypeSoftware = "Software" 73 FileTypeDocumentation = "Documentation" 74 FileTypeOpenSourceLicense = "Open Source License" 75 ) 76 77 func (p ProductFilesService) List(productSlug string) ([]ProductFile, error) { 78 url := fmt.Sprintf("/products/%s/product_files", productSlug) 79 80 var response ProductFilesResponse 81 resp, err := p.client.MakeRequest( 82 "GET", 83 url, 84 http.StatusOK, 85 nil, 86 ) 87 if err != nil { 88 return []ProductFile{}, err 89 } 90 defer resp.Body.Close() 91 92 err = json.NewDecoder(resp.Body).Decode(&response) 93 if err != nil { 94 return []ProductFile{}, err 95 } 96 97 return response.ProductFiles, nil 98 } 99 100 func (p ProductFilesService) ListForRelease(productSlug string, releaseID int) ([]ProductFile, error) { 101 url := fmt.Sprintf( 102 "/products/%s/releases/%d/product_files", 103 productSlug, 104 releaseID, 105 ) 106 107 var response ProductFilesResponse 108 resp, err := p.client.MakeRequest( 109 "GET", 110 url, 111 http.StatusOK, 112 nil, 113 ) 114 if err != nil { 115 return []ProductFile{}, err 116 } 117 defer resp.Body.Close() 118 119 err = json.NewDecoder(resp.Body).Decode(&response) 120 if err != nil { 121 return []ProductFile{}, err 122 } 123 124 return response.ProductFiles, nil 125 } 126 127 func (p ProductFilesService) Get(productSlug string, productFileID int) (ProductFile, error) { 128 url := fmt.Sprintf( 129 "/products/%s/product_files/%d", 130 productSlug, 131 productFileID, 132 ) 133 134 var response ProductFileResponse 135 resp, err := p.client.MakeRequest( 136 "GET", 137 url, 138 http.StatusOK, 139 nil, 140 ) 141 if err != nil { 142 return ProductFile{}, err 143 } 144 defer resp.Body.Close() 145 146 err = json.NewDecoder(resp.Body).Decode(&response) 147 if err != nil { 148 return ProductFile{}, err 149 } 150 151 return response.ProductFile, nil 152 } 153 154 func (p ProductFilesService) GetForRelease(productSlug string, releaseID int, productFileID int) (ProductFile, error) { 155 url := fmt.Sprintf( 156 "/products/%s/releases/%d/product_files/%d", 157 productSlug, 158 releaseID, 159 productFileID, 160 ) 161 162 var response ProductFileResponse 163 resp, err := p.client.MakeRequest( 164 "GET", 165 url, 166 http.StatusOK, 167 nil, 168 ) 169 if err != nil { 170 return ProductFile{}, err 171 } 172 defer resp.Body.Close() 173 174 err = json.NewDecoder(resp.Body).Decode(&response) 175 if err != nil { 176 return ProductFile{}, err 177 } 178 179 return response.ProductFile, nil 180 } 181 182 func (p ProductFilesService) Create(config CreateProductFileConfig) (ProductFile, error) { 183 if config.AWSObjectKey == "" { 184 return ProductFile{}, fmt.Errorf("AWS object key must not be empty") 185 } 186 187 url := fmt.Sprintf("/products/%s/product_files", config.ProductSlug) 188 189 body := createUpdateProductFileBody{ 190 ProductFile: ProductFile{ 191 AWSObjectKey: config.AWSObjectKey, 192 Description: config.Description, 193 DocsURL: config.DocsURL, 194 FileType: config.FileType, 195 FileVersion: config.FileVersion, 196 IncludedFiles: config.IncludedFiles, 197 SHA256: config.SHA256, 198 MD5: config.MD5, 199 Name: config.Name, 200 Platforms: config.Platforms, 201 ReleasedAt: config.ReleasedAt, 202 SystemRequirements: config.SystemRequirements, 203 }, 204 } 205 206 b, err := json.Marshal(body) 207 if err != nil { 208 // Untested as we cannot force an error because we are marshalling 209 // a known-good body 210 return ProductFile{}, err 211 } 212 213 var response ProductFileResponse 214 resp, err := p.client.MakeRequest( 215 "POST", 216 url, 217 http.StatusCreated, 218 bytes.NewReader(b), 219 ) 220 if err != nil { 221 _, ok := err.(ErrTooManyRequests) 222 if ok { 223 return ProductFile{}, fmt.Errorf("You have hit the file creation limit. Please wait before creating more files. Contact pivnet-eng@pivotal.io with additional questions.") 224 } else { 225 return ProductFile{}, err 226 } 227 } 228 defer resp.Body.Close() 229 230 err = json.NewDecoder(resp.Body).Decode(&response) 231 if err != nil { 232 return ProductFile{}, err 233 } 234 235 return response.ProductFile, nil 236 } 237 238 func (p ProductFilesService) Update(productSlug string, productFile ProductFile) (ProductFile, error) { 239 url := fmt.Sprintf("/products/%s/product_files/%d", productSlug, productFile.ID) 240 241 body := createUpdateProductFileBody{ 242 ProductFile: ProductFile{ 243 Description: productFile.Description, 244 FileVersion: productFile.FileVersion, 245 SHA256: productFile.SHA256, 246 MD5: productFile.MD5, 247 Name: productFile.Name, 248 DocsURL: productFile.DocsURL, 249 SystemRequirements: productFile.SystemRequirements, 250 }, 251 } 252 253 b, err := json.Marshal(body) 254 if err != nil { 255 // Untested as we cannot force an error because we are marshalling 256 // a known-good body 257 return ProductFile{}, err 258 } 259 260 var response ProductFileResponse 261 resp, err := p.client.MakeRequest( 262 "PATCH", 263 url, 264 http.StatusOK, 265 bytes.NewReader(b), 266 ) 267 if err != nil { 268 return ProductFile{}, err 269 } 270 defer resp.Body.Close() 271 272 err = json.NewDecoder(resp.Body).Decode(&response) 273 if err != nil { 274 return ProductFile{}, err 275 } 276 277 return response.ProductFile, nil 278 } 279 280 type createUpdateProductFileBody struct { 281 ProductFile ProductFile `json:"product_file"` 282 } 283 284 func (p ProductFilesService) Delete(productSlug string, id int) (ProductFile, error) { 285 url := fmt.Sprintf( 286 "/products/%s/product_files/%d", 287 productSlug, 288 id, 289 ) 290 291 var response ProductFileResponse 292 resp, err := p.client.MakeRequest( 293 "DELETE", 294 url, 295 http.StatusOK, 296 nil, 297 ) 298 if err != nil { 299 return ProductFile{}, err 300 } 301 defer resp.Body.Close() 302 303 err = json.NewDecoder(resp.Body).Decode(&response) 304 if err != nil { 305 return ProductFile{}, err 306 } 307 308 return response.ProductFile, nil 309 } 310 311 func (p ProductFilesService) AddToRelease( 312 productSlug string, 313 releaseID int, 314 productFileID int, 315 ) error { 316 url := fmt.Sprintf( 317 "/products/%s/releases/%d/add_product_file", 318 productSlug, 319 releaseID, 320 ) 321 322 body := createUpdateProductFileBody{ 323 ProductFile: ProductFile{ 324 ID: productFileID, 325 }, 326 } 327 328 b, err := json.Marshal(body) 329 if err != nil { 330 // Untested as we cannot force an error because we are marshalling 331 // a known-good body 332 return err 333 } 334 335 resp, err := p.client.MakeRequest( 336 "PATCH", 337 url, 338 http.StatusNoContent, 339 bytes.NewReader(b), 340 ) 341 if err != nil { 342 return err 343 } 344 defer resp.Body.Close() 345 346 return nil 347 } 348 349 func (p ProductFilesService) RemoveFromRelease( 350 productSlug string, 351 releaseID int, 352 productFileID int, 353 ) error { 354 url := fmt.Sprintf( 355 "/products/%s/releases/%d/remove_product_file", 356 productSlug, 357 releaseID, 358 ) 359 360 body := createUpdateProductFileBody{ 361 ProductFile: ProductFile{ 362 ID: productFileID, 363 }, 364 } 365 366 b, err := json.Marshal(body) 367 if err != nil { 368 // Untested as we cannot force an error because we are marshalling 369 // a known-good body 370 return err 371 } 372 373 resp, err := p.client.MakeRequest( 374 "PATCH", 375 url, 376 http.StatusNoContent, 377 bytes.NewReader(b), 378 ) 379 if err != nil { 380 return err 381 } 382 defer resp.Body.Close() 383 384 return nil 385 } 386 387 func (p ProductFilesService) AddToFileGroup( 388 productSlug string, 389 fileGroupID int, 390 productFileID int, 391 ) error { 392 url := fmt.Sprintf( 393 "/products/%s/file_groups/%d/add_product_file", 394 productSlug, 395 fileGroupID, 396 ) 397 398 body := createUpdateProductFileBody{ 399 ProductFile: ProductFile{ 400 ID: productFileID, 401 }, 402 } 403 404 b, err := json.Marshal(body) 405 if err != nil { 406 // Untested as we cannot force an error because we are marshalling 407 // a known-good body 408 return err 409 } 410 411 resp, err := p.client.MakeRequest( 412 "PATCH", 413 url, 414 http.StatusNoContent, 415 bytes.NewReader(b), 416 ) 417 if err != nil { 418 return err 419 } 420 defer resp.Body.Close() 421 422 return nil 423 } 424 425 func (p ProductFilesService) RemoveFromFileGroup( 426 productSlug string, 427 fileGroupID int, 428 productFileID int, 429 ) error { 430 url := fmt.Sprintf( 431 "/products/%s/file_groups/%d/remove_product_file", 432 productSlug, 433 fileGroupID, 434 ) 435 436 body := createUpdateProductFileBody{ 437 ProductFile: ProductFile{ 438 ID: productFileID, 439 }, 440 } 441 442 b, err := json.Marshal(body) 443 if err != nil { 444 // Untested as we cannot force an error because we are marshalling 445 // a known-good body 446 return err 447 } 448 449 resp, err := p.client.MakeRequest( 450 "PATCH", 451 url, 452 http.StatusNoContent, 453 bytes.NewReader(b), 454 ) 455 if err != nil { 456 return err 457 } 458 defer resp.Body.Close() 459 460 return nil 461 } 462 463 func (p ProductFilesService) DownloadForRelease( 464 location *download.FileInfo, 465 productSlug string, 466 releaseID int, 467 productFileID int, 468 progressWriter io.Writer, 469 ) error { 470 pf, err := p.GetForRelease( 471 productSlug, 472 releaseID, 473 productFileID, 474 ) 475 if err != nil { 476 return fmt.Errorf("GetForRelease: %s", err) 477 } 478 479 downloadLink, err := pf.DownloadLink() 480 if err != nil { 481 return fmt.Errorf("DownloadLink: %s", err) 482 } 483 484 p.client.logger.Debug("Downloading file", logger.Data{"downloadLink": downloadLink}) 485 486 productFileDownloadLinkFetcher := NewProductFileLinkFetcher(downloadLink, p.client) 487 488 p.client.downloader.Bar = download.NewBar() 489 490 err = p.client.downloader.Get( 491 location, 492 productFileDownloadLinkFetcher, 493 progressWriter, 494 ) 495 if err != nil { 496 return fmt.Errorf("Downloader.Get: %s", err) 497 } 498 499 return nil 500 }