github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/catalog.go (about) 1 /* 2 * Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "bytes" 9 "encoding/xml" 10 "errors" 11 "fmt" 12 "io" 13 "math" 14 "net/http" 15 "net/url" 16 "os" 17 "path" 18 "path/filepath" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/vmware/go-vcloud-director/v2/types/v56" 24 "github.com/vmware/go-vcloud-director/v2/util" 25 ) 26 27 const ( 28 defaultPieceSize int64 = 1024 * 1024 29 ) 30 31 type Catalog struct { 32 Catalog *types.Catalog 33 client *Client 34 parent organization 35 } 36 37 func NewCatalog(client *Client) *Catalog { 38 return &Catalog{ 39 Catalog: new(types.Catalog), 40 client: client, 41 } 42 } 43 44 // Delete deletes the Catalog, returning an error if the vCD call fails. 45 // Link to API call: https://code.vmware.com/apis/1046/vmware-cloud-director/doc/doc/operations/DELETE-Catalog.html 46 func (catalog *Catalog) Delete(force, recursive bool) error { 47 48 adminCatalogHREF := catalog.client.VCDHREF 49 catalogID, err := getBareEntityUuid(catalog.Catalog.ID) 50 if err != nil { 51 return err 52 } 53 if catalogID == "" { 54 return fmt.Errorf("empty ID returned for catalog %s", catalog.Catalog.Name) 55 } 56 adminCatalogHREF.Path += "/admin/catalog/" + catalogID 57 58 if force && recursive { 59 // A catalog cannot be removed if it has active tasks, or if any of its items have active tasks 60 err = catalog.consumeTasks() 61 if err != nil { 62 return fmt.Errorf("error while consuming tasks from catalog %s: %s", catalog.Catalog.Name, err) 63 } 64 } 65 66 req := catalog.client.NewRequest(map[string]string{ 67 "force": strconv.FormatBool(force), 68 "recursive": strconv.FormatBool(recursive), 69 }, http.MethodDelete, adminCatalogHREF, nil) 70 71 resp, err := checkResp(catalog.client.Http.Do(req)) 72 if err != nil { 73 return fmt.Errorf("error deleting Catalog %s: %s", catalog.Catalog.Name, err) 74 } 75 task := NewTask(catalog.client) 76 if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil { 77 return fmt.Errorf("error decoding task response: %s", err) 78 } 79 if task.Task.Status == "error" { 80 return fmt.Errorf(combinedTaskErrorMessage(task.Task, fmt.Errorf("catalog %s not properly destroyed", catalog.Catalog.Name))) 81 } 82 return task.WaitTaskCompletion() 83 } 84 85 // consumeTasks will cancel all catalog tasks and the ones related to its items 86 // 1. cancel all tasks associated with the catalog and keep them in a list 87 // 2. find a list of all catalog items 88 // 3. find a list of all tasks associated with the organization, with name = "syncCatalogItem" or "createCatalogItem" 89 // 4. loop through the tasks until we find the ones that belong to one of the items - add them to list in 1. 90 // 5. cancel all the filtered tasks 91 // 6. wait for the task list until all are finished 92 func (catalog *Catalog) consumeTasks() error { 93 allTasks, err := catalog.client.QueryTaskList(map[string]string{ 94 "status": "running,preRunning,queued", 95 }) 96 if err != nil { 97 return fmt.Errorf("error getting task list from catalog %s: %s", catalog.Catalog.Name, err) 98 } 99 var taskList []string 100 addTask := func(status, href string) { 101 if status != "success" && status != "error" && status != "aborted" { 102 quickTask := Task{ 103 client: catalog.client, 104 Task: &types.Task{ 105 HREF: href, 106 }, 107 } 108 err = quickTask.CancelTask() 109 if err != nil { 110 util.Logger.Printf("[consumeTasks] error canceling task: %s\n", err) 111 } 112 taskList = append(taskList, extractUuid(href)) 113 } 114 } 115 if catalog.Catalog.Tasks != nil && len(catalog.Catalog.Tasks.Task) > 0 { 116 for _, task := range catalog.Catalog.Tasks.Task { 117 addTask(task.Status, task.HREF) 118 } 119 } 120 catalogItemRefs, err := catalog.QueryCatalogItemList() 121 if err != nil { 122 return fmt.Errorf("error getting catalog %s items list: %s", catalog.Catalog.Name, err) 123 } 124 for _, task := range allTasks { 125 for _, ref := range catalogItemRefs { 126 catalogItemId := extractUuid(ref.HREF) 127 if extractUuid(task.Object) == catalogItemId { 128 addTask(task.Status, task.HREF) 129 // No break here: the same object can have more than one task 130 } 131 } 132 } 133 _, err = catalog.client.WaitTaskListCompletion(taskList, true) 134 if err != nil { 135 return fmt.Errorf("error while waiting for task list completion for catalog %s: %s", catalog.Catalog.Name, err) 136 } 137 return nil 138 } 139 140 // Envelope is a ovf description root element. File contains information for vmdk files. 141 // Namespace: http://schemas.dmtf.org/ovf/envelope/1 142 // Description: Envelope is a ovf description root element. File contains information for vmdk files.. 143 type Envelope struct { 144 File []struct { 145 HREF string `xml:"href,attr"` 146 ID string `xml:"id,attr"` 147 Size int `xml:"size,attr"` 148 ChunkSize int `xml:"chunkSize,attr"` 149 } `xml:"References>File"` 150 } 151 152 // If catalog item is a valid CatalogItem and the call succeeds, 153 // then the function returns a CatalogItem. If the item does not 154 // exist, then it returns an empty CatalogItem. If the call fails 155 // at any point, it returns an error. 156 // Deprecated: use GetCatalogItemByName instead 157 func (cat *Catalog) FindCatalogItem(catalogItemName string) (CatalogItem, error) { 158 for _, catalogItems := range cat.Catalog.CatalogItems { 159 for _, catalogItem := range catalogItems.CatalogItem { 160 if catalogItem.Name == catalogItemName && catalogItem.Type == "application/vnd.vmware.vcloud.catalogItem+xml" { 161 162 cat := NewCatalogItem(cat.client) 163 164 _, err := cat.client.ExecuteRequest(catalogItem.HREF, http.MethodGet, 165 "", "error retrieving catalog: %s", nil, cat.CatalogItem) 166 return *cat, err 167 } 168 } 169 } 170 171 return CatalogItem{}, nil 172 } 173 174 // UploadOvf uploads an ova/ovf file to a catalog. This method only uploads bits to vCD spool area. 175 // ovaFileName should be the path of OVA or OVF file(not ovf folder) itself. For OVF, 176 // user need to make sure all the files that OVF depends on exist and locate under the same folder. 177 // Returns errors if any occur during upload from vCD or upload process. On upload fail client may need to 178 // remove vCD catalog item which waits for files to be uploaded. Files from ova are extracted to system 179 // temp folder "govcd+random number" and left for inspection on error. 180 func (cat *Catalog) UploadOvf(ovaFileName, itemName, description string, uploadPieceSize int64) (UploadTask, error) { 181 182 // On a very high level the flow is as follows 183 // 1. Makes a POST call to vCD to create the catalog item (also creates a transfer folder in the spool area and as result will give a sparse catalog item resource XML). 184 // 2. Wait for the links to the transfer folder to appear in the resource representation of the catalog item. 185 // 3. Start uploading bits to the transfer folder 186 // 4. Wait on the import task to finish on vCD side -> task success = upload complete 187 188 if *cat == (Catalog{}) { 189 return UploadTask{}, errors.New("catalog can not be empty or nil") 190 } 191 192 ovaFileName, err := validateAndFixFilePath(ovaFileName) 193 if err != nil { 194 return UploadTask{}, err 195 } 196 197 for _, catalogItemName := range getExistingCatalogItems(cat) { 198 if catalogItemName == itemName { 199 return UploadTask{}, fmt.Errorf("catalog item '%s' already exists. Upload with different name", itemName) 200 } 201 } 202 203 isOvf := false 204 fileContentType, err := util.GetFileContentType(ovaFileName) 205 if err != nil { 206 return UploadTask{}, err 207 } 208 if strings.Contains(fileContentType, "text/xml") { 209 isOvf = true 210 } 211 ovfFilePath := ovaFileName 212 tmpDir := path.Dir(ovaFileName) 213 filesAbsPaths := []string{ovfFilePath} 214 if !isOvf { 215 filesAbsPaths, tmpDir, err = util.Unpack(ovaFileName) 216 if err != nil { 217 return UploadTask{}, fmt.Errorf("%s. Unpacked files for checking are accessible in: %s", err, tmpDir) 218 } 219 ovfFilePath, err = getOvfPath(filesAbsPaths) 220 if err != nil { 221 return UploadTask{}, fmt.Errorf("%s. Unpacked files for checking are accessible in: %s", err, tmpDir) 222 } 223 } 224 225 ovfFileDesc, err := getOvf(ovfFilePath) 226 if err != nil { 227 return UploadTask{}, fmt.Errorf("%s. OVF/Unpacked files for checking are accessible in: %s", err, tmpDir) 228 } 229 230 if !isOvf { 231 err = validateOvaContent(filesAbsPaths, &ovfFileDesc, tmpDir) 232 if err != nil { 233 return UploadTask{}, fmt.Errorf("%s. Unpacked files for checking are accessible in: %s", err, tmpDir) 234 } 235 } else { 236 dir := path.Dir(ovfFilePath) 237 for _, fileItem := range ovfFileDesc.File { 238 dependFile := path.Join(dir, fileItem.HREF) 239 dependFile, err := validateAndFixFilePath(dependFile) 240 if err != nil { 241 return UploadTask{}, err 242 } 243 filesAbsPaths = append(filesAbsPaths, dependFile) 244 } 245 } 246 247 catalogItemUploadURL, err := findCatalogItemUploadLink(cat, "application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml") 248 if err != nil { 249 return UploadTask{}, err 250 } 251 252 vappTemplateUrl, err := createItemForUpload(cat.client, catalogItemUploadURL, itemName, description) 253 if err != nil { 254 return UploadTask{}, err 255 } 256 257 vappTemplate, err := queryVappTemplateAndVerifyTask(cat.client, vappTemplateUrl, itemName) 258 if err != nil { 259 return UploadTask{}, err 260 } 261 262 ovfUploadHref, err := getUploadLink(vappTemplate.Files) 263 if err != nil { 264 return UploadTask{}, err 265 } 266 267 err = uploadOvfDescription(cat.client, ovfFilePath, ovfUploadHref) 268 if err != nil { 269 removeCatalogItemOnError(cat.client, vappTemplateUrl, itemName) 270 return UploadTask{}, err 271 } 272 273 vappTemplate, err = waitForTempUploadLinks(cat.client, vappTemplateUrl, itemName) 274 if err != nil { 275 removeCatalogItemOnError(cat.client, vappTemplateUrl, itemName) 276 return UploadTask{}, err 277 } 278 279 progressCallBack, uploadProgress := getProgressCallBackFunction() 280 281 uploadError := *new(error) 282 283 // sending upload process to background, this allows not to lock and return task to client 284 // The error should be captured in uploadError, but just in case, we add a logging for the 285 // main error 286 go func() { 287 err = uploadFiles(cat.client, vappTemplate, &ovfFileDesc, tmpDir, filesAbsPaths, uploadPieceSize, progressCallBack, &uploadError, isOvf) 288 if err != nil { 289 util.Logger.Println(strings.Repeat("*", 80)) 290 util.Logger.Printf("*** [DEBUG - UploadOvf] error calling uploadFiles: %s\n", err) 291 util.Logger.Println(strings.Repeat("*", 80)) 292 } 293 }() 294 295 var task Task 296 for _, item := range vappTemplate.Tasks.Task { 297 task, err = createTaskForVcdImport(cat.client, item.HREF) 298 if err != nil { 299 removeCatalogItemOnError(cat.client, vappTemplateUrl, itemName) 300 return UploadTask{}, err 301 } 302 if task.Task.Status == "error" { 303 removeCatalogItemOnError(cat.client, vappTemplateUrl, itemName) 304 return UploadTask{}, fmt.Errorf("task did not complete succesfully: %s", task.Task.Description) 305 } 306 } 307 308 uploadTask := NewUploadTask(&task, uploadProgress, &uploadError) 309 310 util.Logger.Printf("[TRACE] Upload finished and task for vcd import created. \n") 311 312 return *uploadTask, nil 313 } 314 315 // UploadOvfByLink uploads an OVF file to a catalog from remote URL. 316 // Returns errors if any occur during upload from VCD or upload process. On upload fail client may need to 317 // remove VCD catalog item which is in failed state. 318 func (cat *Catalog) UploadOvfByLink(ovfUrl, itemName, description string) (Task, error) { 319 320 if *cat == (Catalog{}) { 321 return Task{}, errors.New("catalog can not be empty or nil") 322 } 323 324 for _, catalogItemName := range getExistingCatalogItems(cat) { 325 if catalogItemName == itemName { 326 return Task{}, fmt.Errorf("catalog item '%s' already exists. Upload with different name", itemName) 327 } 328 } 329 330 catalogItemUploadURL, err := findCatalogItemUploadLink(cat, "application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml") 331 if err != nil { 332 return Task{}, err 333 } 334 335 vappTemplateUrl, err := createItemWithLink(cat.client, catalogItemUploadURL, itemName, description, ovfUrl) 336 if err != nil { 337 return Task{}, err 338 } 339 340 vappTemplate, err := fetchVappTemplate(cat.client, vappTemplateUrl) 341 if err != nil { 342 return Task{}, err 343 } 344 345 var task Task 346 for _, item := range vappTemplate.Tasks.Task { 347 task, err = createTaskForVcdImport(cat.client, item.HREF) 348 if err != nil { 349 removeCatalogItemOnError(cat.client, vappTemplateUrl, itemName) 350 return Task{}, err 351 } 352 if task.Task.Status == "error" { 353 removeCatalogItemOnError(cat.client, vappTemplateUrl, itemName) 354 return Task{}, fmt.Errorf("task did not complete succesfully: %s", task.Task.Description) 355 } 356 } 357 358 util.Logger.Printf("[TRACE] task for vcd import created. \n") 359 360 return task, nil 361 } 362 363 // CaptureVappTemplate captures a vApp template from an existing vApp 364 func (cat *Catalog) CaptureVappTemplate(captureParams *types.CaptureVAppParams) (*VAppTemplate, error) { 365 task, err := cat.CaptureVappTemplateAsync(captureParams) 366 if err != nil { 367 return nil, err 368 } 369 370 err = task.WaitTaskCompletion() 371 if err != nil { 372 return nil, err 373 } 374 375 if task.Task == nil || task.Task.Owner == nil || task.Task.Owner.HREF == "" { 376 return nil, fmt.Errorf("task does not have Owner HREF populated: %#v", task) 377 } 378 379 // After the task is finished, owner field contains the resulting vApp template 380 return cat.GetVappTemplateByHref(task.Task.Owner.HREF) 381 } 382 383 // CaptureVappTemplateAsync triggers vApp template capturing task and returns it 384 // 385 // Note. If 'CaptureVAppParams.CopyTpmOnInstantiate' is set, it will be unset for VCD versions 386 // before 10.4.2 as it would break API call 387 func (cat *Catalog) CaptureVappTemplateAsync(captureParams *types.CaptureVAppParams) (Task, error) { 388 util.Logger.Printf("[TRACE] Capturing vApp template to catalog %s", cat.Catalog.Name) 389 if captureParams == nil { 390 return Task{}, fmt.Errorf("input CaptureVAppParams cannot be nil") 391 } 392 393 captureTemplateHref := cat.client.VCDHREF 394 captureTemplateHref.Path += fmt.Sprintf("/catalog/%s/action/captureVApp", extractUuid(cat.Catalog.ID)) 395 396 captureParams.Xmlns = types.XMLNamespaceVCloud 397 captureParams.XmlnsNs0 = types.XMLNamespaceOVF 398 399 util.Logger.Printf("[TRACE] Url for capturing vApp template: %s", captureTemplateHref.String()) 400 401 if cat.client.APIVCDMaxVersionIs("< 37.2") { 402 captureParams.CopyTpmOnInstantiate = nil 403 util.Logger.Println("[TRACE] Explicitly unsetting 'CopyTpmOnInstantiate' because it was not supported before VCD 10.4.2") 404 } 405 406 return cat.client.ExecuteTaskRequest(captureTemplateHref.String(), http.MethodPost, 407 types.MimeCaptureVappTemplateParams, "error capturing vApp Template: %s", captureParams) 408 } 409 410 // Upload files for vCD created upload links. Different approach then vmdk file are 411 // chunked (e.g. test.vmdk.000000000, test.vmdk.000000001 or test.vmdk). vmdk files are chunked if 412 // in description file attribute ChunkSize is not zero. 413 // params: 414 // client - client for requests 415 // vappTemplate - parsed from response vApp template 416 // ovfFileDesc - parsed from xml part containing ova files definition 417 // tempPath - path where extracted files are 418 // filesAbsPaths - array of extracted files 419 // uploadPieceSize - size of chunks in which the file will be uploaded to the catalog. 420 // callBack a function with signature //function(bytesUpload, totalSize) to let the caller monitor progress of the upload operation. 421 // uploadError - error to be ready be task 422 func uploadFiles(client *Client, vappTemplate *types.VAppTemplate, ovfFileDesc *Envelope, tempPath string, filesAbsPaths []string, uploadPieceSize int64, progressCallBack func(bytesUpload, totalSize int64), uploadError *error, isOvf bool) error { 423 var uploadedBytes int64 424 for _, item := range vappTemplate.Files.File { 425 if item.BytesTransferred == 0 { 426 number, err := getFileFromDescription(item.Name, ovfFileDesc) 427 if err != nil { 428 util.Logger.Printf("[Error] Error uploading files: %#v", err) 429 *uploadError = err 430 return err 431 } 432 if ovfFileDesc.File[number].ChunkSize != 0 { 433 chunkFilePaths := getChunkedFilePaths(tempPath, ovfFileDesc.File[number].HREF, ovfFileDesc.File[number].Size, ovfFileDesc.File[number].ChunkSize) 434 details := uploadDetails{ 435 uploadLink: item.Link[0].HREF, 436 uploadedBytes: uploadedBytes, 437 fileSizeToUpload: int64(ovfFileDesc.File[number].Size), 438 uploadPieceSize: uploadPieceSize, 439 uploadedBytesForCallback: uploadedBytes, 440 allFilesSize: getAllFileSizeSum(ovfFileDesc), 441 callBack: progressCallBack, 442 uploadError: uploadError, 443 } 444 tempVar, err := uploadMultiPartFile(client, chunkFilePaths, details) 445 if err != nil { 446 util.Logger.Printf("[Error] Error uploading files: %#v", err) 447 *uploadError = err 448 return err 449 } 450 uploadedBytes += tempVar 451 } else { 452 details := uploadDetails{ 453 uploadLink: item.Link[0].HREF, 454 uploadedBytes: 0, 455 fileSizeToUpload: item.Size, 456 uploadPieceSize: uploadPieceSize, 457 uploadedBytesForCallback: uploadedBytes, 458 allFilesSize: getAllFileSizeSum(ovfFileDesc), 459 callBack: progressCallBack, 460 uploadError: uploadError, 461 } 462 tempVar, err := uploadFile(client, findFilePath(filesAbsPaths, item.Name), details) 463 if err != nil { 464 util.Logger.Printf("[Error] Error uploading files: %#v", err) 465 *uploadError = err 466 return err 467 } 468 uploadedBytes += tempVar 469 } 470 } 471 } 472 473 //remove extracted files with temp dir 474 //If isOvf flag is true, means tempPath is origin OVF folder, not extracted, won't delete 475 if !isOvf { 476 err := os.RemoveAll(tempPath) 477 if err != nil { 478 util.Logger.Printf("[Error] Error removing temporary files: %#v", err) 479 *uploadError = err 480 return err 481 } 482 } 483 uploadError = nil 484 return nil 485 } 486 487 func getFileFromDescription(fileToFind string, ovfFileDesc *Envelope) (int, error) { 488 for fileInArray, item := range ovfFileDesc.File { 489 if item.HREF == fileToFind { 490 util.Logger.Printf("[TRACE] getFileFromDescription - found matching file: %s in array: %d\n", fileToFind, fileInArray) 491 return fileInArray, nil 492 } 493 } 494 return -1, errors.New("file expected from vcd didn't match any description file") 495 } 496 497 func getAllFileSizeSum(ovfFileDesc *Envelope) (sizeSum int64) { 498 sizeSum = 0 499 for _, item := range ovfFileDesc.File { 500 sizeSum += int64(item.Size) 501 } 502 return 503 } 504 505 // Uploads chunked ova file for vCD created upload link. 506 // params: 507 // client - client for requests 508 // vappTemplate - parsed from response vApp template 509 // filePaths - all chunked vmdk file paths 510 // uploadDetails - file upload settings and data 511 func uploadMultiPartFile(client *Client, filePaths []string, uDetails uploadDetails) (int64, error) { 512 util.Logger.Printf("[TRACE] Upload multi part file: %v\n, href: %s, size: %v", filePaths, uDetails.uploadLink, uDetails.fileSizeToUpload) 513 514 var uploadedBytes int64 515 516 for i, filePath := range filePaths { 517 util.Logger.Printf("[TRACE] Uploading file: %v\n", i+1) 518 uDetails.uploadedBytesForCallback += uploadedBytes // previous files uploaded size plus current upload size 519 uDetails.uploadedBytes = uploadedBytes 520 tempVar, err := uploadFile(client, filePath, uDetails) 521 if err != nil { 522 return uploadedBytes, err 523 } 524 uploadedBytes += tempVar 525 } 526 return uploadedBytes, nil 527 } 528 529 // Function waits until vCD provides temporary file upload links. 530 func waitForTempUploadLinks(client *Client, vappTemplateUrl *url.URL, newItemName string) (*types.VAppTemplate, error) { 531 var vAppTemplate *types.VAppTemplate 532 var err error 533 for { 534 util.Logger.Printf("[TRACE] Sleep... for 5 seconds.\n") 535 time.Sleep(time.Second * 5) 536 vAppTemplate, err = queryVappTemplateAndVerifyTask(client, vappTemplateUrl, newItemName) 537 if err != nil { 538 return nil, err 539 } 540 if vAppTemplate.Files != nil && len(vAppTemplate.Files.File) > 1 { 541 util.Logger.Printf("[TRACE] upload link prepared.\n") 542 break 543 } 544 } 545 return vAppTemplate, nil 546 } 547 548 func queryVappTemplateAndVerifyTask(client *Client, vappTemplateUrl *url.URL, newItemName string) (*types.VAppTemplate, error) { 549 util.Logger.Printf("[TRACE] Querying vApp template: %s\n", vappTemplateUrl) 550 551 vappTemplateParsed, err := fetchVappTemplate(client, vappTemplateUrl) 552 if err != nil { 553 return nil, err 554 } 555 556 if vappTemplateParsed.Tasks == nil { 557 util.Logger.Printf("[ERROR] the vApp Template %s does not contain tasks, an error happened during upload: %v", vappTemplateUrl, vappTemplateParsed) 558 return vappTemplateParsed, fmt.Errorf("the vApp Template %s does not contain tasks, an error happened during upload", vappTemplateUrl) 559 } 560 561 for _, task := range vappTemplateParsed.Tasks.Task { 562 if task.Status == "error" && newItemName == task.Owner.Name { 563 util.Logger.Printf("[Error] %#v", task.Error) 564 return vappTemplateParsed, fmt.Errorf("error in vcd returned error code: %d, error: %s and message: %s ", task.Error.MajorErrorCode, task.Error.MinorErrorCode, task.Error.Message) 565 } 566 } 567 568 return vappTemplateParsed, nil 569 } 570 571 func fetchVappTemplate(client *Client, vappTemplateUrl *url.URL) (*types.VAppTemplate, error) { 572 util.Logger.Printf("[TRACE] Querying vApp template: %s\n", vappTemplateUrl) 573 574 vappTemplateParsed := &types.VAppTemplate{} 575 576 _, err := client.ExecuteRequest(vappTemplateUrl.String(), http.MethodGet, 577 "", "error fetching vApp template: %s", nil, vappTemplateParsed) 578 if err != nil { 579 return nil, err 580 } 581 582 return vappTemplateParsed, nil 583 } 584 585 // Uploads ovf description file from unarchived provided ova file. As a result vCD will generate temporary upload links which has to be queried later. 586 // Function will return parsed part for upload files from description xml. 587 func uploadOvfDescription(client *Client, ovfFile string, ovfUploadUrl *url.URL) error { 588 util.Logger.Printf("[TRACE] Uploding ovf description with file: %s and url: %s\n", ovfFile, ovfUploadUrl) 589 openedFile, err := os.Open(filepath.Clean(ovfFile)) 590 if err != nil { 591 return err 592 } 593 594 var buf bytes.Buffer 595 ovfReader := io.TeeReader(openedFile, &buf) 596 597 request := client.NewRequest(map[string]string{}, http.MethodPut, *ovfUploadUrl, ovfReader) 598 request.Header.Add("Content-Type", "text/xml") 599 600 _, err = checkResp(client.Http.Do(request)) 601 if err != nil { 602 return err 603 } 604 605 err = openedFile.Close() 606 if err != nil { 607 util.Logger.Printf("[Error] Error closing file: %#v", err) 608 return err 609 } 610 611 return nil 612 } 613 614 func parseOvfFileDesc(file *os.File, ovfFileDesc *Envelope) error { 615 ovfXml, err := io.ReadAll(file) 616 if err != nil { 617 return err 618 } 619 620 err = xml.Unmarshal(ovfXml, &ovfFileDesc) 621 if err != nil { 622 return err 623 } 624 return nil 625 } 626 627 func findCatalogItemUploadLink(catalog *Catalog, applicationType string) (*url.URL, error) { 628 for _, item := range catalog.Catalog.Link { 629 if item.Type == applicationType && item.Rel == "add" { 630 util.Logger.Printf("[TRACE] Found Catalong link for upload: %s\n", item.HREF) 631 632 uploadURL, err := url.ParseRequestURI(item.HREF) 633 if err != nil { 634 return nil, err 635 } 636 637 util.Logger.Printf("[TRACE] findCatalogItemUploadLink - catalog item upload url found: %s \n", uploadURL) 638 return uploadURL, nil 639 } 640 } 641 return nil, errors.New("catalog upload URL not found") 642 } 643 644 func getExistingCatalogItems(catalog *Catalog) (catalogItemNames []string) { 645 for _, catalogItems := range catalog.Catalog.CatalogItems { 646 for _, catalogItem := range catalogItems.CatalogItem { 647 catalogItemNames = append(catalogItemNames, catalogItem.Name) 648 } 649 } 650 return 651 } 652 653 func findFilePath(filesAbsPaths []string, fileName string) string { 654 for _, item := range filesAbsPaths { 655 _, file := filepath.Split(item) 656 if file == fileName { 657 return item 658 } 659 } 660 return "" 661 } 662 663 // Initiates creation of item and returns ovf upload url for created item. 664 func createItemForUpload(client *Client, createHREF *url.URL, catalogItemName string, itemDescription string) (*url.URL, error) { 665 util.Logger.Printf("[TRACE] createItemForUpload: %s, item name: %s, description: %s \n", createHREF, catalogItemName, itemDescription) 666 reqBody := bytes.NewBufferString( 667 "<UploadVAppTemplateParams xmlns=\"" + types.XMLNamespaceVCloud + "\" name=\"" + catalogItemName + "\" >" + 668 "<Description>" + itemDescription + "</Description>" + 669 "</UploadVAppTemplateParams>") 670 671 request := client.NewRequest(map[string]string{}, http.MethodPost, *createHREF, reqBody) 672 request.Header.Add("Content-Type", "application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml") 673 674 response, err := checkResp(client.Http.Do(request)) 675 if err != nil { 676 return nil, err 677 } 678 defer func(Body io.ReadCloser) { 679 err := Body.Close() 680 if err != nil { 681 util.Logger.Printf("error closing response Body [createItemForUpload]: %s", err) 682 } 683 }(response.Body) 684 685 catalogItemParsed := &types.CatalogItem{} 686 if err = decodeBody(types.BodyTypeXML, response, catalogItemParsed); err != nil { 687 return nil, err 688 } 689 690 util.Logger.Printf("[TRACE] Catalog item parsed: %#v\n", catalogItemParsed) 691 692 ovfUploadUrl, err := url.ParseRequestURI(catalogItemParsed.Entity.HREF) 693 if err != nil { 694 return nil, err 695 } 696 697 return ovfUploadUrl, nil 698 } 699 700 // Initiates creation of item in catalog and returns vappTeamplate Url for created item. 701 func createItemWithLink(client *Client, createHREF *url.URL, catalogItemName, itemDescription, vappTemplateRemoteUrl string) (*url.URL, error) { 702 util.Logger.Printf("[TRACE] createItemWithLink: %s, item name: %s, description: %s, vappTemplateRemoteUrl: %s \n", 703 createHREF, catalogItemName, itemDescription, vappTemplateRemoteUrl) 704 705 reqTemplate := `<UploadVAppTemplateParams xmlns="%s" name="%s" sourceHref="%s"><Description>%s</Description></UploadVAppTemplateParams>` 706 reqBody := bytes.NewBufferString(fmt.Sprintf(reqTemplate, types.XMLNamespaceVCloud, catalogItemName, vappTemplateRemoteUrl, itemDescription)) 707 request := client.NewRequest(map[string]string{}, http.MethodPost, *createHREF, reqBody) 708 request.Header.Add("Content-Type", "application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml") 709 710 response, err := checkResp(client.Http.Do(request)) 711 if err != nil { 712 return nil, err 713 } 714 defer func(Body io.ReadCloser) { 715 err := Body.Close() 716 if err != nil { 717 util.Logger.Printf("error closing response Body [createItemWithLink]: %s", err) 718 } 719 }(response.Body) 720 721 catalogItemParsed := &types.CatalogItem{} 722 if err = decodeBody(types.BodyTypeXML, response, catalogItemParsed); err != nil { 723 return nil, err 724 } 725 726 util.Logger.Printf("[TRACE] Catalog item parsed: %#v\n", catalogItemParsed) 727 728 vappTemplateUrl, err := url.ParseRequestURI(catalogItemParsed.Entity.HREF) 729 if err != nil { 730 return nil, err 731 } 732 733 return vappTemplateUrl, nil 734 } 735 736 // Helper method to get path to multi-part files. 737 // For example a file called test.vmdk with total_file_size = 100 bytes and part_size = 40 bytes, implies the file is made of *3* part files. 738 // - test.vmdk.000000000 = 40 bytes 739 // - test.vmdk.000000001 = 40 bytes 740 // - test.vmdk.000000002 = 20 bytes 741 // 742 // Say base_dir = /dummy_path/, and base_file_name = test.vmdk then 743 // the output of this function will be [/dummy_path/test.vmdk.000000000, 744 // /dummy_path/test.vmdk.000000001, /dummy_path/test.vmdk.000000002] 745 func getChunkedFilePaths(baseDir, baseFileName string, totalFileSize, partSize int) []string { 746 var filePaths []string 747 numbParts := math.Ceil(float64(totalFileSize) / float64(partSize)) 748 for i := 0; i < int(numbParts); i++ { 749 temp := "000000000" + strconv.Itoa(i) 750 postfix := temp[len(temp)-9:] 751 filePath := path.Join(baseDir, baseFileName+"."+postfix) 752 filePaths = append(filePaths, filePath) 753 } 754 755 util.Logger.Printf("[TRACE] Chunked files file paths: %s \n", filePaths) 756 return filePaths 757 } 758 759 func getOvfPath(filesAbsPaths []string) (string, error) { 760 for _, filePath := range filesAbsPaths { 761 if filepath.Ext(filePath) == ".ovf" { 762 return filePath, nil 763 } 764 } 765 return "", errors.New("ova is not correct - missing ovf file") 766 } 767 768 func getOvf(ovfFilePath string) (Envelope, error) { 769 openedFile, err := os.Open(filepath.Clean(ovfFilePath)) 770 if err != nil { 771 return Envelope{}, err 772 } 773 774 var ovfFileDesc Envelope 775 err = parseOvfFileDesc(openedFile, &ovfFileDesc) 776 if err != nil { 777 return Envelope{}, err 778 } 779 780 err = openedFile.Close() 781 if err != nil { 782 util.Logger.Printf("[Error] Error closing file: %#v", err) 783 return Envelope{}, err 784 } 785 786 return ovfFileDesc, nil 787 } 788 789 func validateOvaContent(filesAbsPaths []string, ovfFileDesc *Envelope, tempPath string) error { 790 for _, fileDescription := range ovfFileDesc.File { 791 if fileDescription.ChunkSize == 0 { 792 err := checkIfFileMatchesDescription(filesAbsPaths, fileDescription) 793 if err != nil { 794 return err 795 } 796 // check chunked ova content 797 } else { 798 chunkFilePaths := getChunkedFilePaths(tempPath, fileDescription.HREF, fileDescription.Size, fileDescription.ChunkSize) 799 for part, chunkedFilePath := range chunkFilePaths { 800 _, fileName := filepath.Split(chunkedFilePath) 801 chunkedFileSize := fileDescription.Size - part*fileDescription.ChunkSize 802 if chunkedFileSize > fileDescription.ChunkSize { 803 chunkedFileSize = fileDescription.ChunkSize 804 } 805 chunkedFileDescription := struct { 806 HREF string `xml:"href,attr"` 807 ID string `xml:"id,attr"` 808 Size int `xml:"size,attr"` 809 ChunkSize int `xml:"chunkSize,attr"` 810 }{fileName, "", chunkedFileSize, fileDescription.ChunkSize} 811 err := checkIfFileMatchesDescription(filesAbsPaths, chunkedFileDescription) 812 if err != nil { 813 return err 814 } 815 } 816 } 817 } 818 return nil 819 } 820 821 func checkIfFileMatchesDescription(filesAbsPaths []string, fileDescription struct { 822 HREF string `xml:"href,attr"` 823 ID string `xml:"id,attr"` 824 Size int `xml:"size,attr"` 825 ChunkSize int `xml:"chunkSize,attr"` 826 }) error { 827 filePath := findFilePath(filesAbsPaths, fileDescription.HREF) 828 if filePath == "" { 829 return fmt.Errorf("file '%s' described in ovf was not found in ova", fileDescription.HREF) 830 } 831 if fileInfo, err := os.Stat(filePath); err == nil { 832 if fileDescription.Size > 0 && (fileInfo.Size() != int64(fileDescription.Size)) { 833 return fmt.Errorf("file size didn't match described in ovf: %s", filePath) 834 } 835 } else { 836 return err 837 } 838 return nil 839 } 840 841 func removeCatalogItemOnError(client *Client, vappTemplateLink *url.URL, itemName string) { 842 if vappTemplateLink != nil { 843 util.Logger.Printf("[TRACE] Deleting Catalog item %v", vappTemplateLink) 844 845 // wait for task, cancel it and catalog item will be removed. 846 var vAppTemplate *types.VAppTemplate 847 var err error 848 for { 849 util.Logger.Printf("[TRACE] Sleep... for 5 seconds.\n") 850 time.Sleep(time.Second * 5) 851 vAppTemplate, err = queryVappTemplateAndVerifyTask(client, vappTemplateLink, itemName) 852 if err != nil { 853 util.Logger.Printf("[Error] Error deleting Catalog item %s: %s", vappTemplateLink, err) 854 } 855 if vAppTemplate.Tasks == nil { 856 util.Logger.Printf("[Error] Error deleting Catalog item %s: it doesn't contain any task", vappTemplateLink) 857 return 858 } 859 if vAppTemplate.Tasks != nil && len(vAppTemplate.Tasks.Task) > 0 { 860 util.Logger.Printf("[TRACE] Task found. Will try to cancel.\n") 861 break 862 } 863 } 864 865 for _, taskItem := range vAppTemplate.Tasks.Task { 866 if itemName == taskItem.Owner.Name { 867 task := NewTask(client) 868 task.Task = taskItem 869 err = task.CancelTask() 870 if err != nil { 871 util.Logger.Printf("[ERROR] Error canceling task for catalog item upload %#v", err) 872 } 873 } 874 } 875 } else { 876 util.Logger.Printf("[Error] Failed to delete catalog item created with error: %v", vappTemplateLink) 877 } 878 } 879 880 // UploadMediaImage uploads a media image to the catalog 881 func (cat *Catalog) UploadMediaImage(mediaName, mediaDescription, filePath string, uploadPieceSize int64) (UploadTask, error) { 882 return cat.UploadMediaFile(mediaName, mediaDescription, filePath, uploadPieceSize, true) 883 } 884 885 // UploadMediaFile uploads any file to the catalog. 886 // However, if checkFileIsIso is true, only .ISO are allowed. 887 func (cat *Catalog) UploadMediaFile(fileName, mediaDescription, filePath string, uploadPieceSize int64, checkFileIsIso bool) (UploadTask, error) { 888 889 if *cat == (Catalog{}) { 890 return UploadTask{}, errors.New("catalog can not be empty or nil") 891 } 892 893 mediaFilePath, err := validateAndFixFilePath(filePath) 894 if err != nil { 895 return UploadTask{}, err 896 } 897 898 if checkFileIsIso { 899 isISOGood, err := verifyIso(mediaFilePath) 900 if err != nil || !isISOGood { 901 return UploadTask{}, fmt.Errorf("[ERROR] File %s isn't correct iso file: %#v", mediaFilePath, err) 902 } 903 } 904 905 file, e := os.Stat(mediaFilePath) 906 if e != nil { 907 return UploadTask{}, fmt.Errorf("[ERROR] Issue finding file: %#v", e) 908 } 909 fileSize := file.Size() 910 911 for _, catalogItemName := range getExistingCatalogItems(cat) { 912 if catalogItemName == fileName { 913 return UploadTask{}, fmt.Errorf("media item '%s' already exists. Upload with different name", fileName) 914 } 915 } 916 917 catalogItemUploadURL, err := findCatalogItemUploadLink(cat, "application/vnd.vmware.vcloud.media+xml") 918 if err != nil { 919 return UploadTask{}, err 920 } 921 922 media, err := createMedia(cat.client, catalogItemUploadURL.String(), fileName, mediaDescription, fileSize) 923 if err != nil { 924 return UploadTask{}, fmt.Errorf("[ERROR] Issue creating media: %#v", err) 925 } 926 927 createdMedia, err := queryMedia(cat.client, media.Entity.HREF, fileName) 928 if err != nil { 929 return UploadTask{}, err 930 } 931 932 return executeUpload(cat.client, createdMedia, mediaFilePath, fileName, fileSize, uploadPieceSize) 933 } 934 935 // Refresh gets a fresh copy of the catalog from vCD 936 func (cat *Catalog) Refresh() error { 937 if cat == nil || *cat == (Catalog{}) || cat.Catalog.HREF == "" { 938 return fmt.Errorf("cannot refresh, Object is empty or HREF is empty") 939 } 940 941 refreshedCatalog := &types.Catalog{} 942 943 _, err := cat.client.ExecuteRequest(cat.Catalog.HREF, http.MethodGet, 944 "", "error refreshing VDC: %s", nil, refreshedCatalog) 945 if err != nil { 946 return err 947 } 948 cat.Catalog = refreshedCatalog 949 950 return nil 951 } 952 953 // GetCatalogItemByHref finds a CatalogItem by HREF 954 // On success, returns a pointer to the CatalogItem structure and a nil error 955 // On failure, returns a nil pointer and an error 956 func (cat *Catalog) GetCatalogItemByHref(catalogItemHref string) (*CatalogItem, error) { 957 958 catItem := NewCatalogItem(cat.client) 959 960 _, err := cat.client.ExecuteRequest(catalogItemHref, http.MethodGet, 961 "", "error retrieving catalog item: %s", nil, catItem.CatalogItem) 962 if err != nil { 963 return nil, err 964 } 965 return catItem, nil 966 } 967 968 // GetVappTemplateByHref finds a vApp template by HREF 969 // On success, returns a pointer to the vApp template structure and a nil error 970 // On failure, returns a nil pointer and an error 971 func (cat *Catalog) GetVappTemplateByHref(href string) (*VAppTemplate, error) { 972 return getVAppTemplateByHref(cat.client, href) 973 } 974 975 // getVAppTemplateByHref finds a vApp template by HREF 976 // On success, returns a pointer to the vApp template structure and a nil error 977 // On failure, returns a nil pointer and an error 978 func getVAppTemplateByHref(client *Client, href string) (*VAppTemplate, error) { 979 vappTemplate := NewVAppTemplate(client) 980 981 _, err := client.ExecuteRequest(href, http.MethodGet, "", "error retrieving vApp Template: %s", nil, vappTemplate.VAppTemplate) 982 if err != nil { 983 return nil, err 984 } 985 return vappTemplate, nil 986 } 987 988 // GetCatalogItemByName finds a CatalogItem by Name 989 // On success, returns a pointer to the CatalogItem structure and a nil error 990 // On failure, returns a nil pointer and an error 991 func (cat *Catalog) GetCatalogItemByName(catalogItemName string, refresh bool) (*CatalogItem, error) { 992 if refresh { 993 err := cat.Refresh() 994 if err != nil { 995 return nil, err 996 } 997 } 998 for _, catalogItems := range cat.Catalog.CatalogItems { 999 for _, catalogItem := range catalogItems.CatalogItem { 1000 if catalogItem.Name == catalogItemName && catalogItem.Type == "application/vnd.vmware.vcloud.catalogItem+xml" { 1001 return cat.GetCatalogItemByHref(catalogItem.HREF) 1002 } 1003 } 1004 } 1005 return nil, ErrorEntityNotFound 1006 } 1007 1008 // GetVAppTemplateByName finds a VAppTemplate by Name 1009 // On success, returns a pointer to the VAppTemplate structure and a nil error 1010 // On failure, returns a nil pointer and an error 1011 func (cat *Catalog) GetVAppTemplateByName(vAppTemplateName string) (*VAppTemplate, error) { 1012 vAppTemplateQueryResult, err := cat.QueryVappTemplateWithName(vAppTemplateName) 1013 if err != nil { 1014 return nil, err 1015 } 1016 return cat.GetVappTemplateByHref(vAppTemplateQueryResult.HREF) 1017 } 1018 1019 // GetCatalogItemById finds a Catalog Item by ID 1020 // On success, returns a pointer to the CatalogItem structure and a nil error 1021 // On failure, returns a nil pointer and an error 1022 func (cat *Catalog) GetCatalogItemById(catalogItemId string, refresh bool) (*CatalogItem, error) { 1023 if refresh { 1024 err := cat.Refresh() 1025 if err != nil { 1026 return nil, err 1027 } 1028 } 1029 for _, catalogItems := range cat.Catalog.CatalogItems { 1030 for _, catalogItem := range catalogItems.CatalogItem { 1031 if equalIds(catalogItemId, catalogItem.ID, catalogItem.HREF) && catalogItem.Type == "application/vnd.vmware.vcloud.catalogItem+xml" { 1032 return cat.GetCatalogItemByHref(catalogItem.HREF) 1033 } 1034 } 1035 } 1036 return nil, ErrorEntityNotFound 1037 } 1038 1039 // GetVAppTemplateById finds a vApp Template by ID. 1040 // On success, returns a pointer to the VAppTemplate structure and a nil error. 1041 // On failure, returns a nil pointer and an error. 1042 func (cat *Catalog) GetVAppTemplateById(vAppTemplateId string) (*VAppTemplate, error) { 1043 return getVAppTemplateById(cat.client, vAppTemplateId) 1044 } 1045 1046 // getVAppTemplateById finds a vApp Template by ID. 1047 // On success, returns a pointer to the VAppTemplate structure and a nil error. 1048 // On failure, returns a nil pointer and an error. 1049 func getVAppTemplateById(client *Client, vAppTemplateId string) (*VAppTemplate, error) { 1050 vappTemplateHref := client.VCDHREF 1051 vappTemplateHref.Path += "/vAppTemplate/vappTemplate-" + extractUuid(vAppTemplateId) 1052 1053 vappTemplate, err := getVAppTemplateByHref(client, vappTemplateHref.String()) 1054 if err != nil { 1055 return nil, fmt.Errorf("could not find vApp Template with ID %s: %s", vAppTemplateId, err) 1056 } 1057 return vappTemplate, nil 1058 } 1059 1060 // GetCatalogItemByNameOrId finds a Catalog Item by Name or ID. 1061 // On success, returns a pointer to the CatalogItem structure and a nil error 1062 // On failure, returns a nil pointer and an error 1063 func (cat *Catalog) GetCatalogItemByNameOrId(identifier string, refresh bool) (*CatalogItem, error) { 1064 getByName := func(name string, refresh bool) (interface{}, error) { return cat.GetCatalogItemByName(name, refresh) } 1065 getById := func(id string, refresh bool) (interface{}, error) { return cat.GetCatalogItemById(id, refresh) } 1066 entity, err := getEntityByNameOrId(getByName, getById, identifier, refresh) 1067 if entity == nil { 1068 return nil, err 1069 } 1070 return entity.(*CatalogItem), err 1071 } 1072 1073 // GetVAppTemplateByNameOrId finds a vApp Template by Name or ID. 1074 // On success, returns a pointer to the VAppTemplate structure and a nil error 1075 // On failure, returns a nil pointer and an error 1076 func (cat *Catalog) GetVAppTemplateByNameOrId(identifier string, refresh bool) (*VAppTemplate, error) { 1077 getByName := func(name string, refresh bool) (interface{}, error) { return cat.GetVAppTemplateByName(name) } 1078 getById := func(id string, refresh bool) (interface{}, error) { return cat.GetVAppTemplateById(id) } 1079 entity, err := getEntityByNameOrIdSkipNonId(getByName, getById, identifier, refresh) 1080 if entity == nil { 1081 return nil, err 1082 } 1083 return entity.(*VAppTemplate), err 1084 } 1085 1086 // QueryMediaList retrieves a list of media items for the catalog 1087 func (catalog *Catalog) QueryMediaList() ([]*types.MediaRecordType, error) { 1088 typeMedia := "media" 1089 if catalog.client.IsSysAdmin { 1090 typeMedia = "adminMedia" 1091 } 1092 1093 filter := fmt.Sprintf("catalog==%s", url.QueryEscape(catalog.Catalog.HREF)) 1094 results, err := catalog.client.QueryWithNotEncodedParams(nil, map[string]string{"type": typeMedia, "filter": filter, "filterEncoded": "true"}) 1095 if err != nil { 1096 return nil, fmt.Errorf("error querying medias: %s", err) 1097 } 1098 1099 mediaResults := results.Results.MediaRecord 1100 if catalog.client.IsSysAdmin { 1101 mediaResults = results.Results.AdminMediaRecord 1102 } 1103 return mediaResults, nil 1104 } 1105 1106 // getOrgInfo finds the organization to which the catalog belongs, and returns its name and ID 1107 func (catalog *Catalog) getOrgInfo() (*TenantContext, error) { 1108 org := catalog.parent 1109 if org == nil { 1110 return nil, fmt.Errorf("no parent found for catalog %s", catalog.Catalog.Name) 1111 } 1112 1113 return org.tenantContext() 1114 } 1115 1116 func publishToExternalOrganizations(client *Client, url string, tenantContext *TenantContext, publishExternalCatalog types.PublishExternalCatalogParams) error { 1117 url = url + "/action/publishToExternalOrganizations" 1118 1119 publishExternalCatalog.Xmlns = types.XMLNamespaceVCloud 1120 1121 if tenantContext != nil { 1122 client.SetCustomHeader(getTenantContextHeader(tenantContext)) 1123 } 1124 1125 err := client.ExecuteRequestWithoutResponse(url, http.MethodPost, 1126 types.PublishExternalCatalog, "error publishing to external organization: %s", publishExternalCatalog) 1127 1128 if tenantContext != nil { 1129 client.RemoveProvidedCustomHeaders(getTenantContextHeader(tenantContext)) 1130 } 1131 1132 return err 1133 } 1134 1135 // PublishToExternalOrganizations publishes a catalog to external organizations. 1136 func (cat *Catalog) PublishToExternalOrganizations(publishExternalCatalog types.PublishExternalCatalogParams) error { 1137 if cat.Catalog == nil { 1138 return fmt.Errorf("cannot publish to external organization, Object is empty") 1139 } 1140 1141 catalogUrl := cat.Catalog.HREF 1142 if catalogUrl == "nil" || catalogUrl == "" { 1143 return fmt.Errorf("cannot publish to external organization, HREF is empty") 1144 } 1145 1146 err := publishToExternalOrganizations(cat.client, catalogUrl, nil, publishExternalCatalog) 1147 if err != nil { 1148 return err 1149 } 1150 1151 err = cat.Refresh() 1152 if err != nil { 1153 return err 1154 } 1155 1156 return err 1157 } 1158 1159 // elementSync is a low level function that synchronises a Catalog, AdminCatalog, CatalogItem, or Media item 1160 func elementSync(client *Client, elementHref, label string) error { 1161 task, err := elementLaunchSync(client, elementHref, label) 1162 if err != nil { 1163 return err 1164 } 1165 return task.WaitTaskCompletion() 1166 } 1167 1168 // queryMediaList retrieves a list of media items for a given catalog or AdminCatalog 1169 func queryMediaList(client *Client, catalogHref string) ([]*types.MediaRecordType, error) { 1170 typeMedia := "media" 1171 if client.IsSysAdmin { 1172 typeMedia = "adminMedia" 1173 } 1174 1175 filter := fmt.Sprintf("catalog==%s", url.QueryEscape(catalogHref)) 1176 results, err := client.QueryWithNotEncodedParams(nil, map[string]string{"type": typeMedia, "filter": filter, "filterEncoded": "true"}) 1177 if err != nil { 1178 return nil, fmt.Errorf("error querying medias: %s", err) 1179 } 1180 1181 mediaResults := results.Results.MediaRecord 1182 if client.IsSysAdmin { 1183 mediaResults = results.Results.AdminMediaRecord 1184 } 1185 return mediaResults, nil 1186 } 1187 1188 // elementLaunchSync is a low level function that starts synchronisation for Catalog, AdminCatalog, CatalogItem, or Media item 1189 func elementLaunchSync(client *Client, elementHref, label string) (*Task, error) { 1190 util.Logger.Printf("[TRACE] elementLaunchSync '%s' \n", label) 1191 href := elementHref + "/action/sync" 1192 syncTask, err := client.ExecuteTaskRequest(href, http.MethodPost, 1193 "", "error synchronizing "+label+": %s", nil) 1194 1195 if err != nil { 1196 // This process may fail due to a possible race condition: a synchronisation process may start in background 1197 // after we check for existing tasks (in the function that called this one) 1198 // and before we run the request in this function. 1199 // In a Terraform vcd_subscribed_catalog operation, the completeness of the synchronisation 1200 // will be ensured at the next refresh. 1201 if strings.Contains(err.Error(), "LIBRARY_ITEM_SYNC") { 1202 util.Logger.Printf("[SYNC FAILURE] error when launching synchronisation: %s\n", err) 1203 return nil, nil 1204 } 1205 return nil, err 1206 } 1207 return &syncTask, nil 1208 } 1209 1210 // QueryTaskList retrieves a list of tasks associated to the Catalog 1211 func (catalog *Catalog) QueryTaskList(filter map[string]string) ([]*types.QueryResultTaskRecordType, error) { 1212 var newFilter = map[string]string{ 1213 "object": catalog.Catalog.HREF, 1214 } 1215 for k, v := range filter { 1216 newFilter[k] = v 1217 } 1218 return catalog.client.QueryTaskList(newFilter) 1219 } 1220 1221 // GetCatalogByHref allows retrieving a catalog from HREF, without a fully qualified Org object 1222 func (client *Client) GetCatalogByHref(catalogHref string) (*Catalog, error) { 1223 catalogHref = strings.Replace(catalogHref, "/api/admin/catalog", "/api/catalog", 1) 1224 1225 cat := NewCatalog(client) 1226 1227 _, err := client.ExecuteRequest(catalogHref, http.MethodGet, 1228 "", "error retrieving catalog: %s", nil, cat.Catalog) 1229 1230 if err != nil { 1231 return nil, err 1232 } 1233 // Setting the catalog parent, necessary to handle the tenant context 1234 org := NewOrg(client) 1235 for _, link := range cat.Catalog.Link { 1236 if link.Rel == "up" && link.Type == types.MimeOrg { 1237 _, err = client.ExecuteRequest(link.HREF, http.MethodGet, 1238 "", "error retrieving parent Org: %s", nil, org.Org) 1239 if err != nil { 1240 return nil, fmt.Errorf("error retrieving catalog parent: %s", err) 1241 } 1242 break 1243 } 1244 } 1245 cat.parent = org 1246 return cat, nil 1247 } 1248 1249 // GetCatalogById allows retrieving a catalog from ID, without a fully qualified Org object 1250 func (client *Client) GetCatalogById(catalogId string) (*Catalog, error) { 1251 href, err := url.JoinPath(client.VCDHREF.String(), "catalog", extractUuid(catalogId)) 1252 if err != nil { 1253 return nil, err 1254 } 1255 return client.GetCatalogByHref(href) 1256 } 1257 1258 // GetCatalogByName allows retrieving a catalog from name, without a fully qualified Org object 1259 func (client *Client) GetCatalogByName(parentOrg, catalogName string) (*Catalog, error) { 1260 catalogs, err := queryCatalogList(client, nil) 1261 if err != nil { 1262 return nil, err 1263 } 1264 var parentOrgs []string 1265 for _, cat := range catalogs { 1266 if cat.Name == catalogName && cat.OrgName == parentOrg { 1267 return client.GetCatalogByHref(cat.HREF) 1268 } 1269 if cat.Name == catalogName { 1270 parentOrgs = append(parentOrgs, cat.OrgName) 1271 } 1272 } 1273 parents := "" 1274 if len(parentOrgs) > 0 { 1275 parents = fmt.Sprintf(" - Found catalog %s in Orgs %v", catalogName, parentOrgs) 1276 } 1277 return nil, fmt.Errorf("no catalog '%s' found in Org %s%s", catalogName, parentOrg, parents) 1278 } 1279 1280 // WaitForTasks waits for the catalog's tasks to complete 1281 func (cat *Catalog) WaitForTasks() error { 1282 if ResourceInProgress(cat.Catalog.Tasks) { 1283 err := WaitResource(func() (*types.TasksInProgress, error) { 1284 err := cat.Refresh() 1285 if err != nil { 1286 return nil, err 1287 } 1288 return cat.Catalog.Tasks, nil 1289 }) 1290 return err 1291 } 1292 return nil 1293 }