github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/admincatalog.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  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    15  	"github.com/vmware/go-vcloud-director/v2/util"
    16  )
    17  
    18  // AdminCatalog is a admin view of a VMware Cloud Director Catalog
    19  // To be able to get an AdminCatalog representation, users must have
    20  // admin credentials to the System org. AdminCatalog is used
    21  // for creating, updating, and deleting a Catalog.
    22  // Definition: https://code.vmware.com/apis/220/vcloud#/doc/doc/types/AdminCatalogType.html
    23  type AdminCatalog struct {
    24  	AdminCatalog *types.AdminCatalog
    25  	client       *Client
    26  	parent       organization
    27  }
    28  
    29  func NewAdminCatalog(client *Client) *AdminCatalog {
    30  	return &AdminCatalog{
    31  		AdminCatalog: new(types.AdminCatalog),
    32  		client:       client,
    33  	}
    34  }
    35  
    36  func NewAdminCatalogWithParent(client *Client, parent organization) *AdminCatalog {
    37  	return &AdminCatalog{
    38  		AdminCatalog: new(types.AdminCatalog),
    39  		client:       client,
    40  		parent:       parent,
    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/220/vcloud#/doc/doc/operations/DELETE-Catalog.html
    46  func (adminCatalog *AdminCatalog) Delete(force, recursive bool) error {
    47  	catalog := NewCatalog(adminCatalog.client)
    48  	catalog.Catalog = &adminCatalog.AdminCatalog.Catalog
    49  	return catalog.Delete(force, recursive)
    50  }
    51  
    52  // Update updates the Catalog definition from current Catalog struct contents.
    53  // Any differences that may be legally applied will be updated.
    54  // Returns an error if the call to vCD fails. Update automatically performs
    55  // a refresh with the admin catalog it gets back from the rest api
    56  // Link to API call: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/PUT-Catalog.html
    57  func (adminCatalog *AdminCatalog) Update() error {
    58  	reqCatalog := &types.Catalog{
    59  		Name:        adminCatalog.AdminCatalog.Catalog.Name,
    60  		Description: adminCatalog.AdminCatalog.Description,
    61  	}
    62  	vcomp := &types.AdminCatalog{
    63  		Xmlns:                  types.XMLNamespaceVCloud,
    64  		Catalog:                *reqCatalog,
    65  		CatalogStorageProfiles: adminCatalog.AdminCatalog.CatalogStorageProfiles,
    66  		IsPublished:            adminCatalog.AdminCatalog.IsPublished,
    67  	}
    68  	catalog := &types.AdminCatalog{}
    69  	_, err := adminCatalog.client.ExecuteRequest(adminCatalog.AdminCatalog.HREF, http.MethodPut,
    70  		"application/vnd.vmware.admin.catalog+xml", "error updating catalog: %s", vcomp, catalog)
    71  	adminCatalog.AdminCatalog = catalog
    72  	return err
    73  }
    74  
    75  // UploadOvf uploads an ova file to a catalog. This method only uploads bits to vCD spool area.
    76  // Returns errors if any occur during upload from vCD or upload process. On upload fail client may need to
    77  // remove vCD catalog item which waits for files to be uploaded. Files from ova are extracted to system
    78  // temp folder "govcd+random number" and left for inspection on error.
    79  func (adminCatalog *AdminCatalog) UploadOvf(ovaFileName, itemName, description string, uploadPieceSize int64) (UploadTask, error) {
    80  	catalog := NewCatalog(adminCatalog.client)
    81  	catalog.Catalog = &adminCatalog.AdminCatalog.Catalog
    82  	catalog.parent = adminCatalog.parent
    83  	return catalog.UploadOvf(ovaFileName, itemName, description, uploadPieceSize)
    84  }
    85  
    86  // Refresh fetches a fresh copy of the Admin Catalog
    87  func (adminCatalog *AdminCatalog) Refresh() error {
    88  	if *adminCatalog == (AdminCatalog{}) || adminCatalog.AdminCatalog.HREF == "" {
    89  		return fmt.Errorf("cannot refresh, Object is empty or HREF is empty")
    90  	}
    91  
    92  	refreshedCatalog := &types.AdminCatalog{}
    93  
    94  	_, err := adminCatalog.client.ExecuteRequest(adminCatalog.AdminCatalog.HREF, http.MethodGet,
    95  		"", "error refreshing VDC: %s", nil, refreshedCatalog)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	adminCatalog.AdminCatalog = refreshedCatalog
   100  
   101  	return nil
   102  }
   103  
   104  // getOrgInfo finds the organization to which the admin catalog belongs, and returns its name and ID
   105  func (adminCatalog *AdminCatalog) getOrgInfo() (*TenantContext, error) {
   106  	return adminCatalog.getTenantContext()
   107  }
   108  
   109  // PublishToExternalOrganizations publishes a catalog to external organizations.
   110  func (cat *AdminCatalog) PublishToExternalOrganizations(publishExternalCatalog types.PublishExternalCatalogParams) error {
   111  	if cat.AdminCatalog == nil {
   112  		return fmt.Errorf("cannot publish to external organization, Object is empty")
   113  	}
   114  
   115  	url := cat.AdminCatalog.HREF
   116  	if url == "nil" || url == "" {
   117  		return fmt.Errorf("cannot publish to external organization, HREF is empty")
   118  	}
   119  
   120  	tenantContext, err := cat.getTenantContext()
   121  	if err != nil {
   122  		return fmt.Errorf("cannot publish to external organization, tenant context error: %s", err)
   123  	}
   124  
   125  	err = publishToExternalOrganizations(cat.client, url, tenantContext, publishExternalCatalog)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	err = cat.Refresh()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	return err
   136  }
   137  
   138  // CreateCatalogFromSubscriptionAsync creates a new catalog by subscribing to a published catalog
   139  // Parameter subscription needs to be filled manually
   140  func (org *AdminOrg) CreateCatalogFromSubscriptionAsync(subscription types.ExternalCatalogSubscription,
   141  	storageProfiles *types.CatalogStorageProfiles,
   142  	catalogName, password string, localCopy bool) (*AdminCatalog, error) {
   143  
   144  	// If the receiving Org doesn't have any VDCs, it means that there is no storage that can be used
   145  	// by a catalog
   146  	if len(org.AdminOrg.Vdcs.Vdcs) == 0 {
   147  		return nil, fmt.Errorf("org %s does not have any storage to support a catalog", org.AdminOrg.Name)
   148  	}
   149  	href := ""
   150  
   151  	// The subscribed catalog creation is like a regular catalog creation, with the
   152  	// difference that the subscription details are filled in
   153  	for _, link := range org.AdminOrg.Link {
   154  		if link.Rel == "add" && link.Type == types.MimeAdminCatalog {
   155  			href = link.HREF
   156  			break
   157  		}
   158  	}
   159  	if href == "" {
   160  		return nil, fmt.Errorf("catalog creation link not found for org %s", org.AdminOrg.Name)
   161  	}
   162  	adminCatalog := NewAdminCatalog(org.client)
   163  	reqCatalog := &types.Catalog{
   164  		Name: catalogName,
   165  	}
   166  	adminCatalog.AdminCatalog = &types.AdminCatalog{
   167  		Xmlns:                  types.XMLNamespaceVCloud,
   168  		Catalog:                *reqCatalog,
   169  		CatalogStorageProfiles: storageProfiles,
   170  		ExternalCatalogSubscription: &types.ExternalCatalogSubscription{
   171  			LocalCopy:                localCopy,
   172  			Password:                 password,
   173  			Location:                 subscription.Location,
   174  			SubscribeToExternalFeeds: true,
   175  		},
   176  	}
   177  
   178  	adminCatalog.AdminCatalog.ExternalCatalogSubscription.Password = password
   179  	adminCatalog.AdminCatalog.ExternalCatalogSubscription.LocalCopy = localCopy
   180  	_, err := org.client.ExecuteRequest(href, http.MethodPost, types.MimeAdminCatalog,
   181  		"error subscribing to catalog: %s", adminCatalog.AdminCatalog, adminCatalog.AdminCatalog)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	// Before returning, check that there are no failing tasks
   186  	err = adminCatalog.Refresh()
   187  	if err != nil {
   188  		return nil, fmt.Errorf("error refreshing subscribed catalog %s: %s", catalogName, err)
   189  	}
   190  	if adminCatalog.AdminCatalog.Tasks != nil {
   191  		msg := ""
   192  		for _, task := range adminCatalog.AdminCatalog.Tasks.Task {
   193  			if task.Status == "error" {
   194  				if task.Error != nil {
   195  					msg = task.Error.Error()
   196  				}
   197  				return nil, fmt.Errorf("error while subscribing catalog %s (task %s): %s", catalogName, task.Name, msg)
   198  			}
   199  			if task.Tasks != nil {
   200  				for _, subTask := range task.Tasks.Task {
   201  					if subTask.Status == "error" {
   202  						if subTask.Error != nil {
   203  							msg = subTask.Error.Error()
   204  						}
   205  						return nil, fmt.Errorf("error while subscribing catalog %s (subTask %s): %s", catalogName, subTask.Name, msg)
   206  					}
   207  
   208  				}
   209  			}
   210  		}
   211  	}
   212  	return adminCatalog, nil
   213  }
   214  
   215  // FullSubscriptionUrl returns the subscription URL from a publishing catalog
   216  // adding the HOST if needed
   217  func (cat *AdminCatalog) FullSubscriptionUrl() (string, error) {
   218  	err := cat.Refresh()
   219  	if err != nil {
   220  		return "", err
   221  	}
   222  	if cat.AdminCatalog.PublishExternalCatalogParams == nil {
   223  		return "", fmt.Errorf("AdminCatalog %s has no publishing parameters", cat.AdminCatalog.Name)
   224  	}
   225  	subscriptionUrl, err := buildFullUrl(cat.AdminCatalog.PublishExternalCatalogParams.CatalogPublishedUrl, cat.AdminCatalog.HREF)
   226  	if err != nil {
   227  		return "", err
   228  	}
   229  	return subscriptionUrl, nil
   230  }
   231  
   232  // buildFullUrl gets a (possibly incomplete) URL and returns it completed, using the provided HREF as basis
   233  func buildFullUrl(subscriptionUrl, href string) (string, error) {
   234  	var err error
   235  	if !IsValidUrl(subscriptionUrl) {
   236  		// Get the entity base URL
   237  		cutPosition := strings.Index(href, "/api")
   238  		host := href[:cutPosition]
   239  		subscriptionUrl, err = url.JoinPath(host, subscriptionUrl)
   240  		if err != nil {
   241  			return "", err
   242  		}
   243  	}
   244  	return subscriptionUrl, nil
   245  }
   246  
   247  // IsValidUrl returns true if the given URL is complete and usable
   248  func IsValidUrl(str string) bool {
   249  	u, err := url.Parse(str)
   250  	return err == nil && u.Scheme != "" && u.Host != ""
   251  }
   252  
   253  // CreateCatalogFromSubscription is a wrapper around CreateCatalogFromSubscriptionAsync
   254  // After catalog creation, it waits for the import tasks to complete within a given timeout
   255  func (org *AdminOrg) CreateCatalogFromSubscription(subscription types.ExternalCatalogSubscription,
   256  	storageProfiles *types.CatalogStorageProfiles,
   257  	catalogName, password string, localCopy bool, timeout time.Duration) (*AdminCatalog, error) {
   258  	noTimeout := timeout == 0
   259  	adminCatalog, err := org.CreateCatalogFromSubscriptionAsync(subscription, storageProfiles, catalogName, password, localCopy)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	start := time.Now()
   264  	for noTimeout || time.Since(start) < timeout {
   265  		if noTimeout {
   266  			util.Logger.Printf("[TRACE] [CreateCatalogFromSubscription] no timeout given - Elapsed %s", time.Since(start))
   267  		}
   268  		err = adminCatalog.Refresh()
   269  		if err != nil {
   270  			return nil, err
   271  		}
   272  		if ResourceComplete(adminCatalog.AdminCatalog.Tasks) {
   273  			return adminCatalog, nil
   274  		}
   275  	}
   276  	return nil, fmt.Errorf("adminCatalog %s still not complete after %s", adminCatalog.AdminCatalog.Name, timeout)
   277  }
   278  
   279  // WaitForTasks waits for the catalog's tasks to complete
   280  func (cat *AdminCatalog) WaitForTasks() error {
   281  	if ResourceInProgress(cat.AdminCatalog.Tasks) {
   282  		err := WaitResource(func() (*types.TasksInProgress, error) {
   283  			err := cat.Refresh()
   284  			if err != nil {
   285  				return nil, err
   286  			}
   287  			return cat.AdminCatalog.Tasks, nil
   288  		})
   289  		return err
   290  	}
   291  	return nil
   292  }
   293  
   294  // Sync synchronises a subscribed AdminCatalog
   295  func (cat *AdminCatalog) Sync() error {
   296  	// if the catalog was not subscribed, return
   297  	if cat.AdminCatalog.ExternalCatalogSubscription == nil || cat.AdminCatalog.ExternalCatalogSubscription.Location == "" {
   298  		return nil
   299  	}
   300  	// The sync operation is only available for Catalog, not AdminCatalog.
   301  	// We use the embedded Catalog object for this purpose
   302  	catalogHref, err := cat.GetCatalogHref()
   303  	if err != nil || catalogHref == "" {
   304  		return fmt.Errorf("empty catalog HREF for admin catalog %s", cat.AdminCatalog.Name)
   305  	}
   306  	err = cat.WaitForTasks()
   307  	if err != nil {
   308  		return err
   309  	}
   310  	return elementSync(cat.client, catalogHref, "admin catalog")
   311  }
   312  
   313  // LaunchSync starts synchronisation of a subscribed AdminCatalog
   314  func (cat *AdminCatalog) LaunchSync() (*Task, error) {
   315  	err := checkIfSubscribedCatalog(cat)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  	// The sync operation is only available for Catalog, not AdminCatalog.
   320  	// We use the embedded Catalog object for this purpose
   321  	catalogHref, err := cat.GetCatalogHref()
   322  	if err != nil || catalogHref == "" {
   323  		return nil, fmt.Errorf("empty catalog HREF for admin catalog %s", cat.AdminCatalog.Name)
   324  	}
   325  	err = cat.WaitForTasks()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	return elementLaunchSync(cat.client, catalogHref, "admin catalog")
   330  }
   331  
   332  // GetCatalogHref retrieves the regular catalog HREF from an admin catalog
   333  func (cat *AdminCatalog) GetCatalogHref() (string, error) {
   334  	href := ""
   335  	for _, link := range cat.AdminCatalog.Link {
   336  		if link.Rel == "alternate" && link.Type == types.MimeCatalog {
   337  			href = link.HREF
   338  			break
   339  		}
   340  	}
   341  	if href == "" {
   342  		return "", fmt.Errorf("no regular Catalog HREF found for admin Catalog %s", cat.AdminCatalog.Name)
   343  	}
   344  	return href, nil
   345  }
   346  
   347  // QueryVappTemplateList returns a list of vApp templates for the given catalog
   348  func (catalog *AdminCatalog) QueryVappTemplateList() ([]*types.QueryResultVappTemplateType, error) {
   349  	return queryVappTemplateListWithFilter(catalog.client, map[string]string{"catalogName": catalog.AdminCatalog.Name})
   350  }
   351  
   352  // QueryMediaList retrieves a list of media items for the Admin Catalog
   353  func (catalog *AdminCatalog) QueryMediaList() ([]*types.MediaRecordType, error) {
   354  	return queryMediaList(catalog.client, catalog.AdminCatalog.HREF)
   355  }
   356  
   357  // LaunchSynchronisationVappTemplates starts synchronisation of a list of vApp templates
   358  func (cat *AdminCatalog) LaunchSynchronisationVappTemplates(nameList []string) ([]*Task, error) {
   359  	return launchSynchronisationVappTemplates(cat, nameList, true)
   360  }
   361  
   362  // launchSynchronisationVappTemplates waits for existing tasks to complete and then starts synchronisation for a list of vApp templates
   363  // optionally checking for running tasks
   364  // TODO: re-implement without the undocumented task-related fields
   365  func launchSynchronisationVappTemplates(cat *AdminCatalog, nameList []string, checkForRunningTasks bool) ([]*Task, error) {
   366  	err := checkIfSubscribedCatalog(cat)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  	util.Logger.Printf("[TRACE] launchSynchronisationVappTemplates - AdminCatalog '%s' - 'make_local_copy=%v]\n", cat.AdminCatalog.Name, cat.AdminCatalog.ExternalCatalogSubscription.LocalCopy)
   371  	var taskList []*Task
   372  
   373  	for _, element := range nameList {
   374  		var queryResultCatalogItem *types.QueryResultCatalogItemType
   375  
   376  		if checkForRunningTasks {
   377  			queryResultVappTemplate, err := cat.QueryVappTemplateWithName(element)
   378  			if err != nil {
   379  				return nil, err
   380  			}
   381  			err = checkIfTaskComplete(cat.client, queryResultVappTemplate.Task, queryResultVappTemplate.TaskStatus)
   382  			if err != nil {
   383  				return nil, err
   384  			}
   385  			queryResultCatalogItem = &types.QueryResultCatalogItemType{
   386  				HREF:        queryResultVappTemplate.CatalogItem,
   387  				ID:          extractUuid(queryResultVappTemplate.CatalogItem),
   388  				Type:        types.MimeCatalogItem,
   389  				Entity:      queryResultVappTemplate.HREF,
   390  				EntityName:  queryResultVappTemplate.Name,
   391  				EntityType:  "vapptemplate",
   392  				Catalog:     cat.AdminCatalog.HREF,
   393  				CatalogName: cat.AdminCatalog.Name,
   394  				Status:      queryResultVappTemplate.Status,
   395  				Name:        queryResultVappTemplate.Name,
   396  			}
   397  		} else {
   398  			queryResultCatalogItem, err = cat.QueryCatalogItem(element)
   399  			if err != nil {
   400  				return nil, fmt.Errorf("error retrieving catalog item %s: %s", element, err)
   401  			}
   402  		}
   403  		task, err := queryResultCatalogItemToCatalogItem(cat.client, queryResultCatalogItem).LaunchSync()
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  		if task != nil {
   408  			taskList = append(taskList, task)
   409  		}
   410  	}
   411  	return taskList, nil
   412  }
   413  
   414  // LaunchSynchronisationAllVappTemplates waits for existing tasks to complete and then starts synchronisation of all vApp templates for a given catalog
   415  // TODO: re-implement without the undocumented task-related fields
   416  func (cat *AdminCatalog) LaunchSynchronisationAllVappTemplates() ([]*Task, error) {
   417  	err := checkIfSubscribedCatalog(cat)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  	util.Logger.Printf("[TRACE] AdminCatalog '%s' LaunchSynchronisationAllVappTemplates - 'make_local_copy=%v]\n", cat.AdminCatalog.Name, cat.AdminCatalog.ExternalCatalogSubscription.LocalCopy)
   422  	vappTemplatesList, err := cat.QueryVappTemplateList()
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	var nameList []string
   427  	for _, element := range vappTemplatesList {
   428  		err = checkIfTaskComplete(cat.client, element.Task, element.TaskStatus)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  		nameList = append(nameList, element.Name)
   433  	}
   434  	// Launch synchronisation for each item, without checking for running tasks, as it was already done in this function
   435  	return launchSynchronisationVappTemplates(cat, nameList, false)
   436  }
   437  
   438  func checkIfTaskComplete(client *Client, taskHref, taskStatus string) error {
   439  	complete := taskStatus == "" || isTaskCompleteOrError(taskStatus)
   440  	if !complete {
   441  		task, err := client.GetTaskById(taskHref)
   442  		if err != nil {
   443  			return err
   444  		}
   445  		err = task.WaitTaskCompletion()
   446  		if err != nil {
   447  			return err
   448  		}
   449  	}
   450  	return nil
   451  }
   452  
   453  func checkIfSubscribedCatalog(catalog *AdminCatalog) error {
   454  	err := catalog.Refresh()
   455  	if err != nil {
   456  		return err
   457  	}
   458  	if catalog.AdminCatalog.ExternalCatalogSubscription == nil || catalog.AdminCatalog.ExternalCatalogSubscription.Location == "" {
   459  		return fmt.Errorf("catalog '%s' is not subscribed", catalog.AdminCatalog.Name)
   460  	}
   461  	return nil
   462  }
   463  
   464  // LaunchSynchronisationMediaItems waits for existing tasks to complete and then starts synchronisation of a list of media items
   465  // TODO: re-implement without the undocumented task-related fields
   466  func (cat *AdminCatalog) LaunchSynchronisationMediaItems(nameList []string) ([]*Task, error) {
   467  	err := checkIfSubscribedCatalog(cat)
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  	util.Logger.Printf("[TRACE] AdminCatalog '%s' LaunchSynchronisationMediaItems\n", cat.AdminCatalog.Name)
   472  	var taskList []*Task
   473  	mediaList, err := cat.QueryMediaList()
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	var actionList []string
   478  
   479  	var found = make(map[string]string)
   480  	for _, element := range mediaList {
   481  		if contains(element.Name, nameList) {
   482  			complete := element.TaskStatus == "" || isTaskCompleteOrError(element.TaskStatus)
   483  			if !complete {
   484  				if element.Task != "" {
   485  					task, err := cat.client.GetTaskById(element.Task)
   486  					if err != nil {
   487  						return nil, err
   488  					}
   489  					err = task.WaitTaskCompletion()
   490  					if err != nil {
   491  						return nil, err
   492  					}
   493  				}
   494  			}
   495  			util.Logger.Printf("scheduling for synchronisation Media item %s with catalog item HREF %s\n", element.Name, element.CatalogItem)
   496  			actionList = append(actionList, element.CatalogItem)
   497  			found[element.Name] = element.CatalogItem
   498  		}
   499  	}
   500  	if len(actionList) < len(nameList) {
   501  		var foundList []string
   502  		for k := range found {
   503  			foundList = append(foundList, k)
   504  		}
   505  		return nil, fmt.Errorf("%d names provided [%v] but %d actions scheduled [%v]", len(nameList), nameList, len(actionList), foundList)
   506  	}
   507  	for _, element := range actionList {
   508  		util.Logger.Printf("synchronising Media catalog item HREF %s\n", element)
   509  		catalogItem, err := cat.GetCatalogItemByHref(element)
   510  		if err != nil {
   511  			return nil, err
   512  		}
   513  		task, err := catalogItem.LaunchSync()
   514  		if err != nil {
   515  			return nil, err
   516  		}
   517  		if task != nil {
   518  			taskList = append(taskList, task)
   519  		}
   520  	}
   521  	return taskList, nil
   522  }
   523  
   524  // LaunchSynchronisationAllMediaItems waits for existing tasks to complete and then starts synchronisation of all media items for a given catalog
   525  // TODO re-implement without the non-documented task-related fields
   526  func (cat *AdminCatalog) LaunchSynchronisationAllMediaItems() ([]*Task, error) {
   527  	err := checkIfSubscribedCatalog(cat)
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	util.Logger.Printf("[TRACE] AdminCatalog '%s' LaunchSynchronisationAllMediaItems\n", cat.AdminCatalog.Name)
   532  	var taskList []*Task
   533  	mediaList, err := cat.QueryMediaList()
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  	for _, element := range mediaList {
   538  		if isTaskRunning(element.TaskStatus) {
   539  			task, err := cat.client.GetTaskByHREF(element.Task)
   540  			if err != nil {
   541  				return nil, err
   542  			}
   543  			err = task.WaitTaskCompletion()
   544  			if err != nil {
   545  				return nil, err
   546  			}
   547  		}
   548  		catalogItem, err := cat.GetCatalogItemByHref(element.CatalogItem)
   549  		if err != nil {
   550  			return nil, err
   551  		}
   552  		task, err := catalogItem.LaunchSync()
   553  		if err != nil {
   554  			return nil, err
   555  		}
   556  		if task != nil {
   557  			taskList = append(taskList, task)
   558  		}
   559  	}
   560  	return taskList, nil
   561  }
   562  
   563  // GetCatalogItemByHref finds a CatalogItem by HREF
   564  // On success, returns a pointer to the CatalogItem structure and a nil error
   565  // On failure, returns a nil pointer and an error
   566  func (cat *AdminCatalog) GetCatalogItemByHref(catalogItemHref string) (*CatalogItem, error) {
   567  	catItem := NewCatalogItem(cat.client)
   568  
   569  	_, err := cat.client.ExecuteRequest(catalogItemHref, http.MethodGet,
   570  		"", "error retrieving catalog item: %s", nil, catItem.CatalogItem)
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  	return catItem, nil
   575  }
   576  
   577  // UpdateSubscriptionParams modifies the subscription parameters of an already subscribed catalog
   578  func (catalog *AdminCatalog) UpdateSubscriptionParams(params types.ExternalCatalogSubscription) error {
   579  	err := checkIfSubscribedCatalog(catalog)
   580  	if err != nil {
   581  		return err
   582  	}
   583  	var href string
   584  	for _, link := range catalog.AdminCatalog.Link {
   585  		if link.Rel == "subscribeToExternalCatalog" && link.Type == types.MimeSubscribeToExternalCatalog {
   586  			href = link.HREF
   587  			break
   588  		}
   589  	}
   590  	if href == "" {
   591  		return fmt.Errorf("catalog subscription link not found for catalog %s", catalog.AdminCatalog.Name)
   592  	}
   593  	_, err = catalog.client.ExecuteRequest(href, http.MethodPost, types.MimeAdminCatalog,
   594  		"error subscribing to catalog: %s", params, nil)
   595  	if err != nil {
   596  		return err
   597  	}
   598  	return catalog.Refresh()
   599  }
   600  
   601  // QueryTaskList retrieves a list of tasks associated to the Admin Catalog
   602  func (catalog *AdminCatalog) QueryTaskList(filter map[string]string) ([]*types.QueryResultTaskRecordType, error) {
   603  	catalogHref, err := catalog.GetCatalogHref()
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  	if filter == nil {
   608  		filter = make(map[string]string)
   609  	}
   610  	filter["object"] = catalogHref
   611  	return catalog.client.QueryTaskList(filter)
   612  }
   613  
   614  // GetAdminCatalogByHref allows retrieving a catalog from HREF, without a fully qualified AdminOrg object
   615  func (client *Client) GetAdminCatalogByHref(catalogHref string) (*AdminCatalog, error) {
   616  	catalogHref = strings.Replace(catalogHref, "/api/catalog", "/api/admin/catalog", 1)
   617  
   618  	cat := NewAdminCatalog(client)
   619  
   620  	_, err := client.ExecuteRequest(catalogHref, http.MethodGet,
   621  		"", "error retrieving catalog: %s", nil, cat.AdminCatalog)
   622  
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  
   627  	// Setting the catalog parent, necessary to handle the tenant context
   628  	org := NewAdminOrg(client)
   629  	for _, link := range cat.AdminCatalog.Link {
   630  		if link.Rel == "up" && link.Type == types.MimeAdminOrg {
   631  			_, err = client.ExecuteRequest(link.HREF, http.MethodGet,
   632  				"", "error retrieving parent Org: %s", nil, org.AdminOrg)
   633  			if err != nil {
   634  				return nil, fmt.Errorf("error retrieving catalog parent: %s", err)
   635  			}
   636  			break
   637  		}
   638  	}
   639  
   640  	cat.parent = org
   641  	return cat, nil
   642  }
   643  
   644  // QueryCatalogRecords given a catalog name, retrieves the catalogRecords that match its name
   645  // Returns a list of catalog records for such name, empty list if none was found
   646  func (client *Client) QueryCatalogRecords(name string, ctx TenantContext) ([]*types.CatalogRecord, error) {
   647  	util.Logger.Printf("[DEBUG] QueryCatalogRecords")
   648  
   649  	var filter string
   650  	if name != "" {
   651  		filter = fmt.Sprintf("name==%s", url.QueryEscape(name))
   652  	}
   653  
   654  	var tenantHeaders map[string]string
   655  
   656  	if client.IsSysAdmin && ctx.OrgId != "" && ctx.OrgName != "" {
   657  		// Set tenant context headers just for the query
   658  		tenantHeaders = map[string]string{
   659  			types.HeaderAuthContext:   ctx.OrgName,
   660  			types.HeaderTenantContext: ctx.OrgId,
   661  		}
   662  	}
   663  
   664  	queryType := types.QtCatalog
   665  
   666  	results, err := client.cumulativeQueryWithHeaders(queryType, nil, map[string]string{
   667  		"type":          queryType,
   668  		"filter":        filter,
   669  		"filterEncoded": "true",
   670  	}, tenantHeaders)
   671  	if err != nil {
   672  		return nil, err
   673  	}
   674  
   675  	catalogs := results.Results.CatalogRecord
   676  
   677  	util.Logger.Printf("[DEBUG] QueryCatalogRecords returned with : %#v (%d) and error: %v", catalogs, len(catalogs), err)
   678  	return catalogs, nil
   679  }
   680  
   681  // GetAdminCatalogById allows retrieving a catalog from ID, without a fully qualified AdminOrg object
   682  func (client *Client) GetAdminCatalogById(catalogId string) (*AdminCatalog, error) {
   683  	href, err := url.JoinPath(client.VCDHREF.String(), "admin", "catalog", extractUuid(catalogId))
   684  	if err != nil {
   685  		return nil, err
   686  	}
   687  	return client.GetAdminCatalogByHref(href)
   688  }
   689  
   690  // GetAdminCatalogByName allows retrieving a catalog from name, without a fully qualified AdminOrg object
   691  func (client *Client) GetAdminCatalogByName(parentOrg, catalogName string) (*AdminCatalog, error) {
   692  	catalogs, err := queryCatalogList(client, nil)
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  	var parentOrgs []string
   697  	for _, cat := range catalogs {
   698  		if cat.Name == catalogName && cat.OrgName == parentOrg {
   699  			return client.GetAdminCatalogByHref(cat.HREF)
   700  		}
   701  		if cat.Name == catalogName {
   702  			parentOrgs = append(parentOrgs, cat.OrgName)
   703  		}
   704  	}
   705  	parents := ""
   706  	if len(parentOrgs) > 0 {
   707  		parents = fmt.Sprintf(" - Found catalog %s in Orgs %v", catalogName, parentOrgs)
   708  	}
   709  	return nil, fmt.Errorf("no catalog '%s' found in Org %s%s", catalogName, parentOrg, parents)
   710  }