github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/adminorg.go (about)

     1  /*
     2   * Copyright 2020 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  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/vmware/go-vcloud-director/v2/util"
    16  
    17  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    18  )
    19  
    20  // AdminOrg gives an admin representation of an org.
    21  // Administrators can delete and update orgs with an admin org object.
    22  // AdminOrg includes all members of the Org element, and adds several
    23  // elements that can be viewed and modified only by system administrators.
    24  // Definition: https://code.vmware.com/apis/220/vcloud#/doc/doc/types/AdminOrgType.html
    25  type AdminOrg struct {
    26  	AdminOrg      *types.AdminOrg
    27  	client        *Client
    28  	TenantContext *TenantContext
    29  }
    30  
    31  func NewAdminOrg(cli *Client) *AdminOrg {
    32  	return &AdminOrg{
    33  		AdminOrg: new(types.AdminOrg),
    34  		client:   cli,
    35  	}
    36  }
    37  
    38  // CreateCatalog creates a catalog with given name and description under
    39  // the given organization. Returns an AdminCatalog that contains a creation
    40  // task.
    41  // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/POST-CreateCatalog.html
    42  func (adminOrg *AdminOrg) CreateCatalog(name, description string) (AdminCatalog, error) {
    43  	adminCatalog, err := adminOrg.CreateCatalogWithStorageProfile(name, description, nil)
    44  	if err != nil {
    45  		return AdminCatalog{}, err
    46  	}
    47  	adminCatalog.parent = adminOrg
    48  
    49  	err = adminCatalog.Refresh()
    50  	if err != nil {
    51  		return AdminCatalog{}, err
    52  	}
    53  	// Make sure that the creation task is finished
    54  	err = adminCatalog.WaitForTasks()
    55  	if err != nil {
    56  		return AdminCatalog{}, err
    57  	}
    58  	err = adminCatalog.WaitForTasks()
    59  	if err != nil {
    60  		return AdminCatalog{}, err
    61  	}
    62  	return *adminCatalog, nil
    63  }
    64  
    65  // CreateCatalogWithStorageProfile is like CreateCatalog, but allows to specify storage profile
    66  func (adminOrg *AdminOrg) CreateCatalogWithStorageProfile(name, description string, storageProfiles *types.CatalogStorageProfiles) (*AdminCatalog, error) {
    67  	adminCatalog, err := CreateCatalogWithStorageProfile(adminOrg.client, adminOrg.AdminOrg.Link, name, description, storageProfiles)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	adminCatalogWithParent := NewAdminCatalogWithParent(adminOrg.client, adminOrg)
    72  	adminCatalogWithParent.AdminCatalog = adminCatalog.AdminCatalog
    73  	return adminCatalogWithParent, nil
    74  }
    75  
    76  // GetAllVDCs returns all depending VDCs for a particular Org
    77  func (adminOrg *AdminOrg) GetAllVDCs(refresh bool) ([]*Vdc, error) {
    78  	if refresh {
    79  		err := adminOrg.Refresh()
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  	}
    84  
    85  	allVdcs := make([]*Vdc, len(adminOrg.AdminOrg.Vdcs.Vdcs))
    86  	for vdcIndex, vdc := range adminOrg.AdminOrg.Vdcs.Vdcs {
    87  		vdc, err := adminOrg.GetVDCByHref(vdc.HREF)
    88  		if err != nil {
    89  			return nil, fmt.Errorf("error retrieving VDC '%s': %s", vdc.Vdc.Name, err)
    90  		}
    91  		allVdcs[vdcIndex] = vdc
    92  
    93  	}
    94  
    95  	return allVdcs, nil
    96  }
    97  
    98  // GetAllStorageProfileReferences traverses all depending VDCs and returns a slice of storage profile references
    99  // available in those VDCs
   100  func (adminOrg *AdminOrg) GetAllStorageProfileReferences(refresh bool) ([]*types.Reference, error) {
   101  	if refresh {
   102  		err := adminOrg.Refresh()
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  	}
   107  
   108  	allVdcs, err := adminOrg.GetAllVDCs(refresh)
   109  	if err != nil {
   110  		return nil, fmt.Errorf("could not retrieve storage profile references: %s", err)
   111  	}
   112  
   113  	allStorageProfileReferences := make([]*types.Reference, 0)
   114  	for _, vdc := range allVdcs {
   115  		if len(vdc.Vdc.VdcStorageProfiles.VdcStorageProfile) > 0 {
   116  			allStorageProfileReferences = append(allStorageProfileReferences, vdc.Vdc.VdcStorageProfiles.VdcStorageProfile...)
   117  		}
   118  	}
   119  
   120  	return allStorageProfileReferences, nil
   121  }
   122  
   123  // GetStorageProfileReferenceById finds storage profile reference by specified ID in Org or returns ErrorEntityNotFound
   124  func (adminOrg *AdminOrg) GetStorageProfileReferenceById(id string, refresh bool) (*types.Reference, error) {
   125  	allStorageProfiles, err := adminOrg.GetAllStorageProfileReferences(refresh)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("error getting all storage profiles: %s", err)
   128  	}
   129  
   130  	for _, storageProfileReference := range allStorageProfiles {
   131  		if storageProfileReference.ID == id {
   132  			return storageProfileReference, nil
   133  		}
   134  	}
   135  
   136  	return nil, fmt.Errorf("%s: storage profile with ID '%s' not found in Org '%s'",
   137  		ErrorEntityNotFound, id, adminOrg.AdminOrg.Name)
   138  }
   139  
   140  // Deletes the org, returning an error if the vCD call fails.
   141  // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/DELETE-Organization.html
   142  func (adminOrg *AdminOrg) Delete(force bool, recursive bool) error {
   143  	if force && recursive {
   144  		//undeploys vapps
   145  		err := adminOrg.undeployAllVApps()
   146  		if err != nil {
   147  			return fmt.Errorf("error could not undeploy: %s", err)
   148  		}
   149  		//removes vapps
   150  		err = adminOrg.removeAllVApps()
   151  		if err != nil {
   152  			return fmt.Errorf("error could not remove vapp: %s", err)
   153  		}
   154  		//removes catalogs
   155  		err = adminOrg.removeCatalogs()
   156  		if err != nil {
   157  			return fmt.Errorf("error could not remove all catalogs: %s", err)
   158  		}
   159  		//removes networks
   160  		err = adminOrg.removeAllOrgNetworks()
   161  		if err != nil {
   162  			return fmt.Errorf("error could not remove all networks: %s", err)
   163  		}
   164  		//removes org vdcs
   165  		err = adminOrg.removeAllOrgVDCs()
   166  		if err != nil {
   167  			return fmt.Errorf("error could not remove all vdcs: %s", err)
   168  		}
   169  	}
   170  	// Disable org
   171  	err := adminOrg.Disable()
   172  	if err != nil {
   173  		return fmt.Errorf("error disabling Org %s: %s", adminOrg.AdminOrg.Name, err)
   174  	}
   175  	// Get admin HREF
   176  	orgHREF, err := url.ParseRequestURI(adminOrg.AdminOrg.HREF)
   177  	if err != nil {
   178  		return fmt.Errorf("error getting AdminOrg HREF %s : %s", adminOrg.AdminOrg.HREF, err)
   179  	}
   180  	req := adminOrg.client.NewRequest(map[string]string{
   181  		"force":     strconv.FormatBool(force),
   182  		"recursive": strconv.FormatBool(recursive),
   183  	}, http.MethodDelete, *orgHREF, nil)
   184  	resp, err := checkResp(adminOrg.client.Http.Do(req))
   185  	if err != nil {
   186  		return fmt.Errorf("error deleting Org %s: %s", adminOrg.AdminOrg.ID, err)
   187  	}
   188  
   189  	task := NewTask(adminOrg.client)
   190  	if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil {
   191  		return fmt.Errorf("error decoding task response: %s", err)
   192  	}
   193  	return task.WaitTaskCompletion()
   194  }
   195  
   196  // Disables the org. Returns an error if the call to vCD fails.
   197  // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/POST-DisableOrg.html
   198  func (adminOrg *AdminOrg) Disable() error {
   199  	orgHREF, err := url.ParseRequestURI(adminOrg.AdminOrg.HREF)
   200  	if err != nil {
   201  		return fmt.Errorf("error getting AdminOrg HREF %s : %s", adminOrg.AdminOrg.HREF, err)
   202  	}
   203  	orgHREF.Path += "/action/disable"
   204  
   205  	return adminOrg.client.ExecuteRequestWithoutResponse(orgHREF.String(), http.MethodPost, "", "error disabling organization: %s", nil)
   206  }
   207  
   208  // Updates the Org definition from current org struct contents.
   209  // Any differences that may be legally applied will be updated.
   210  // Returns an error if the call to vCD fails.
   211  // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/PUT-Organization.html
   212  func (adminOrg *AdminOrg) Update() (Task, error) {
   213  	vcomp := &types.AdminOrg{
   214  		Xmlns:       types.XMLNamespaceVCloud,
   215  		Name:        adminOrg.AdminOrg.Name,
   216  		IsEnabled:   adminOrg.AdminOrg.IsEnabled,
   217  		FullName:    adminOrg.AdminOrg.FullName,
   218  		Description: adminOrg.AdminOrg.Description,
   219  		OrgSettings: adminOrg.AdminOrg.OrgSettings,
   220  	}
   221  
   222  	// Same workaround used in Org creation, where OrgGeneralSettings properties
   223  	// are not set unless UseServerBootSequence is also set
   224  	if vcomp.OrgSettings.OrgGeneralSettings != nil {
   225  		vcomp.OrgSettings.OrgGeneralSettings.UseServerBootSequence = true
   226  	}
   227  
   228  	// Return the task
   229  	return adminOrg.client.ExecuteTaskRequest(adminOrg.AdminOrg.HREF, http.MethodPut,
   230  		"application/vnd.vmware.admin.organization+xml", "error updating Org: %s", vcomp)
   231  }
   232  
   233  // Undeploys every vapp within an organization
   234  func (adminOrg *AdminOrg) undeployAllVApps() error {
   235  	for _, vdcs := range adminOrg.AdminOrg.Vdcs.Vdcs {
   236  		adminVdcHREF, err := url.Parse(vdcs.HREF)
   237  		if err != nil {
   238  			return err
   239  		}
   240  		vdc, err := adminOrg.getVdcByAdminHREF(adminVdcHREF)
   241  		if err != nil {
   242  			return fmt.Errorf("error retrieving vapp with url: %s and with error %s", adminVdcHREF.Path, err)
   243  		}
   244  		err = vdc.undeployAllVdcVApps()
   245  		if err != nil {
   246  			return fmt.Errorf("error deleting vapp: %s", err)
   247  		}
   248  	}
   249  	return nil
   250  }
   251  
   252  // Deletes every vapp within an organization
   253  func (adminOrg *AdminOrg) removeAllVApps() error {
   254  	for _, vdcs := range adminOrg.AdminOrg.Vdcs.Vdcs {
   255  		adminVdcHREF, err := url.Parse(vdcs.HREF)
   256  		if err != nil {
   257  			return err
   258  		}
   259  		vdc, err := adminOrg.getVdcByAdminHREF(adminVdcHREF)
   260  		if err != nil {
   261  			return fmt.Errorf("error retrieving vapp with url: %s and with error %s", adminVdcHREF.Path, err)
   262  		}
   263  		err = vdc.removeAllVdcVApps()
   264  		if err != nil {
   265  			return fmt.Errorf("error deleting vapp: %s", err)
   266  		}
   267  	}
   268  	return nil
   269  }
   270  
   271  // Given an adminorg with a valid HREF, the function refetches the adminorg
   272  // and updates the user's adminorg data. Otherwise if the function fails,
   273  // it returns an error.  Users should use refresh whenever they have
   274  // a stale org due to the creation/update/deletion of a resource
   275  // within the org or the org itself.
   276  func (adminOrg *AdminOrg) Refresh() error {
   277  	if *adminOrg == (AdminOrg{}) {
   278  		return fmt.Errorf("cannot refresh, Object is empty")
   279  	}
   280  
   281  	// Empty struct before a new unmarshal, otherwise we end up with duplicate
   282  	// elements in slices.
   283  	unmarshalledAdminOrg := &types.AdminOrg{}
   284  
   285  	_, err := adminOrg.client.ExecuteRequest(adminOrg.AdminOrg.HREF, http.MethodGet,
   286  		"", "error refreshing organization: %s", nil, unmarshalledAdminOrg)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	adminOrg.AdminOrg = unmarshalledAdminOrg
   291  
   292  	return nil
   293  }
   294  
   295  // Gets a vdc within org associated with an admin vdc url
   296  func (adminOrg *AdminOrg) getVdcByAdminHREF(adminVdcUrl *url.URL) (*Vdc, error) {
   297  	// get non admin vdc path
   298  	vdcURL := adminOrg.client.VCDHREF
   299  	vdcURL.Path += strings.Split(adminVdcUrl.Path, "/api/admin")[1] //gets id
   300  
   301  	vdc := NewVdc(adminOrg.client)
   302  
   303  	vdc.parent = adminOrg
   304  
   305  	_, err := adminOrg.client.ExecuteRequest(vdcURL.String(), http.MethodGet,
   306  		"", "error retrieving vdc: %s", nil, vdc.Vdc)
   307  
   308  	return vdc, err
   309  }
   310  
   311  // Removes all vdcs in a org
   312  func (adminOrg *AdminOrg) removeAllOrgVDCs() error {
   313  	for _, vdcs := range adminOrg.AdminOrg.Vdcs.Vdcs {
   314  
   315  		adminVdcUrl := adminOrg.client.VCDHREF
   316  		splitVdcId := strings.Split(vdcs.HREF, "/api/vdc/")
   317  		if len(splitVdcId) == 1 {
   318  			adminVdcUrl.Path += "/admin/vdc/" + strings.Split(vdcs.HREF, "/api/admin/vdc/")[1] + "/action/disable"
   319  		} else {
   320  			adminVdcUrl.Path += "/admin/vdc/" + splitVdcId[1] + "/action/disable"
   321  		}
   322  
   323  		req := adminOrg.client.NewRequest(map[string]string{}, http.MethodPost, adminVdcUrl, nil)
   324  		_, err := checkResp(adminOrg.client.Http.Do(req))
   325  		if err != nil {
   326  			return fmt.Errorf("error disabling vdc: %s", err)
   327  		}
   328  		// Get admin vdc HREF for normal deletion
   329  		adminVdcUrl.Path = strings.Split(adminVdcUrl.Path, "/action/disable")[0]
   330  		req = adminOrg.client.NewRequest(map[string]string{
   331  			"recursive": "true",
   332  			"force":     "true",
   333  		}, http.MethodDelete, adminVdcUrl, nil)
   334  		resp, err := checkResp(adminOrg.client.Http.Do(req))
   335  		if err != nil {
   336  			return fmt.Errorf("error deleting vdc: %s", err)
   337  		}
   338  		task := NewTask(adminOrg.client)
   339  		if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil {
   340  			return fmt.Errorf("error decoding task response: %s", err)
   341  		}
   342  		if task.Task.Status == "error" {
   343  			return fmt.Errorf("vdc not properly destroyed")
   344  		}
   345  		err = task.WaitTaskCompletion()
   346  		if err != nil {
   347  			return fmt.Errorf("couldn't finish removing vdc %s", err)
   348  		}
   349  
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  // Removes All networks in the org
   356  func (adminOrg *AdminOrg) removeAllOrgNetworks() error {
   357  	for _, networks := range adminOrg.AdminOrg.Networks.Networks {
   358  		// Get Network HREF
   359  		networkHREF := adminOrg.client.VCDHREF
   360  		networkHREF.Path += "/admin/network/" + strings.Split(networks.HREF, "/api/admin/network/")[1] //gets id
   361  
   362  		task, err := adminOrg.client.ExecuteTaskRequest(networkHREF.String(), http.MethodDelete,
   363  			"", "error deleting network: %s", nil)
   364  		if err != nil {
   365  			return err
   366  		}
   367  
   368  		if task.Task.Status == "error" {
   369  			return fmt.Errorf("network not properly destroyed")
   370  		}
   371  		err = task.WaitTaskCompletion()
   372  		if err != nil {
   373  			return fmt.Errorf("couldn't finish removing network %s", err)
   374  		}
   375  	}
   376  	return nil
   377  }
   378  
   379  // removeCatalogs force removal of all organization catalogs
   380  func (adminOrg *AdminOrg) removeCatalogs() error {
   381  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   382  		isCatalogFromSameOrg, err := isCatalogFromSameOrg(adminOrg, catalog.Name)
   383  		if err != nil {
   384  			return fmt.Errorf("error deleting catalog: %s", err)
   385  		}
   386  		if isCatalogFromSameOrg {
   387  			// Get Catalog HREF
   388  			catalogHREF := adminOrg.client.VCDHREF
   389  			catalogHREF.Path += "/admin/catalog/" + strings.Split(catalog.HREF, "/api/admin/catalog/")[1] //gets id
   390  			req := adminOrg.client.NewRequest(map[string]string{
   391  				"force":     "true",
   392  				"recursive": "true",
   393  			}, http.MethodDelete, catalogHREF, nil)
   394  			_, err := checkResp(adminOrg.client.Http.Do(req))
   395  			if err != nil {
   396  				return fmt.Errorf("error deleting catalog: %s, %s", err, catalogHREF.Path)
   397  			}
   398  		}
   399  	}
   400  	return nil
   401  
   402  }
   403  
   404  // isCatalogFromSameOrg checks if catalog is in same Org. Shared catalogs from other Org are showed as normal one
   405  // in some API responses.
   406  func isCatalogFromSameOrg(adminOrg *AdminOrg, catalogName string) (bool, error) {
   407  	foundCatalogs, err := adminOrg.FindAdminCatalogRecords(catalogName)
   408  	if err != nil {
   409  		return false, err
   410  	}
   411  
   412  	if len(foundCatalogs) == 1 {
   413  		return true, nil
   414  	}
   415  	return false, nil
   416  }
   417  
   418  // FindAdminCatalogRecords uses catalog name to return AdminCatalogRecord information.
   419  func (adminOrg *AdminOrg) FindAdminCatalogRecords(name string) ([]*types.CatalogRecord, error) {
   420  	util.Logger.Printf("[DEBUG] FindAdminCatalogRecords with name: %s and org name: %s", name, adminOrg.AdminOrg.Name)
   421  	results, err := adminOrg.client.QueryWithNotEncodedParams(nil, map[string]string{
   422  		"type":          "adminCatalog",
   423  		"filter":        fmt.Sprintf("name==%s;orgName==%s", url.QueryEscape(name), url.QueryEscape(adminOrg.AdminOrg.Name)),
   424  		"filterEncoded": "true",
   425  	})
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  
   430  	util.Logger.Printf("[DEBUG] FindAdminCatalogRecords returned with : %#v and error: %s", results.Results.AdminCatalogRecord, err)
   431  	return results.Results.AdminCatalogRecord, nil
   432  }
   433  
   434  // Given a valid catalog name, FindAdminCatalog returns an AdminCatalog object.
   435  // If no catalog is found, then returns an empty AdminCatalog and no error.
   436  // Otherwise it returns an error. Function allows user to use an AdminOrg
   437  // to also fetch a Catalog. If user does not have proper credentials to
   438  // perform administrator tasks then function returns an error.
   439  // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/GET-Catalog-AdminView.html
   440  // Deprecated: Use adminOrg.GetAdminCatalog instead
   441  func (adminOrg *AdminOrg) FindAdminCatalog(catalogName string) (AdminCatalog, error) {
   442  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   443  		// Get Catalog HREF
   444  		if catalog.Name == catalogName {
   445  			adminCatalog := NewAdminCatalog(adminOrg.client)
   446  			_, err := adminOrg.client.ExecuteRequest(catalog.HREF, http.MethodGet,
   447  				"", "error retrieving catalog: %s", nil, adminCatalog.AdminCatalog)
   448  			// The request was successful
   449  			return *adminCatalog, err
   450  		}
   451  	}
   452  	return AdminCatalog{}, nil
   453  }
   454  
   455  // Given a valid catalog name, FindCatalog returns a Catalog object.
   456  // If no catalog is found, then returns an empty catalog and no error.
   457  // Otherwise it returns an error. Function allows user to use an AdminOrg
   458  // to also fetch a Catalog.
   459  // Deprecated: Use adminOrg.GetCatalogByName instead
   460  func (adminOrg *AdminOrg) FindCatalog(catalogName string) (Catalog, error) {
   461  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   462  		// Get Catalog HREF
   463  		if catalog.Name == catalogName {
   464  			catalogURL := adminOrg.client.VCDHREF
   465  			catalogURL.Path += "/catalog/" + strings.Split(catalog.HREF, "/api/admin/catalog/")[1] //gets id
   466  
   467  			cat := NewCatalog(adminOrg.client)
   468  
   469  			_, err := adminOrg.client.ExecuteRequest(catalogURL.String(), http.MethodGet,
   470  				"", "error retrieving catalog: %s", nil, cat.Catalog)
   471  
   472  			// The request was successful
   473  			return *cat, err
   474  		}
   475  	}
   476  	return Catalog{}, nil
   477  }
   478  
   479  // GetCatalogByHref  finds a Catalog by HREF
   480  // On success, returns a pointer to the Catalog structure and a nil error
   481  // On failure, returns a nil pointer and an error
   482  func (adminOrg *AdminOrg) GetCatalogByHref(catalogHref string) (*Catalog, error) {
   483  	splitByAdminHREF := strings.Split(catalogHref, "/api/admin")
   484  
   485  	// admin user and normal user will have different urls
   486  	var catalogHREF string
   487  	if len(splitByAdminHREF) == 1 {
   488  		catalogHREF = catalogHref
   489  	} else {
   490  		catalogHREF = splitByAdminHREF[0] + "/api" + splitByAdminHREF[1]
   491  	}
   492  
   493  	cat := NewCatalog(adminOrg.client)
   494  
   495  	_, err := adminOrg.client.ExecuteRequest(catalogHREF, http.MethodGet,
   496  		"", "error retrieving catalog: %s", nil, cat.Catalog)
   497  
   498  	if err != nil {
   499  		return nil, err
   500  	}
   501  	cat.parent = adminOrg
   502  	// The request was successful
   503  	return cat, nil
   504  }
   505  
   506  // GetCatalogByName  finds a Catalog by Name
   507  // On success, returns a pointer to the Catalog structure and a nil error
   508  // On failure, returns a nil pointer and an error
   509  func (adminOrg *AdminOrg) GetCatalogByName(catalogName string, refresh bool) (*Catalog, error) {
   510  
   511  	if refresh {
   512  		err := adminOrg.Refresh()
   513  		if err != nil {
   514  			return nil, err
   515  		}
   516  	}
   517  
   518  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   519  		if catalog.Name == catalogName {
   520  			return adminOrg.GetCatalogByHref(catalog.HREF)
   521  		}
   522  	}
   523  	return nil, ErrorEntityNotFound
   524  }
   525  
   526  // Extracts an UUID from a string, regardless of surrounding text, returns the last found occurrence
   527  // Returns an empty string if no UUID was found
   528  func extractUuid(input string) string {
   529  	reGetID := regexp.MustCompile(`([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})`)
   530  	matchListId := reGetID.FindAllStringSubmatch(input, -1)
   531  	if len(matchListId) > 0 && len(matchListId[0]) > 0 {
   532  		return matchListId[len(matchListId)-1][1]
   533  	}
   534  	return ""
   535  }
   536  
   537  // equalIds compares two IDs and return true if they are the same
   538  // The comparison happens by extracting the bare UUID from both the
   539  // wanted ID and the found one.
   540  // When the found ID is empty, it used the HREF for such comparison,
   541  // This function is useful when the reference structure in the parent lookup list
   542  // may lack the ID (such as in Org.Links, AdminOrg.Catalogs) or has an ID
   543  // that is only a UUID without prefixes (such as in CatalogItem list)
   544  //
   545  // wantedId is the input string to compare
   546  // foundId is the ID field in the reference record (can be empty)
   547  // foundHref is the HREF field in the reference record (should never be empty)
   548  func equalIds(wantedId, foundId, foundHref string) bool {
   549  
   550  	wantedUuid := extractUuid(wantedId)
   551  	foundUuid := ""
   552  
   553  	if wantedUuid == "" {
   554  		return false
   555  	}
   556  	if foundId != "" {
   557  		// In some entities, the ID is a simple UUID without prefix
   558  		foundUuid = extractUuid(foundId)
   559  	} else {
   560  		foundUuid = extractUuid(foundHref)
   561  	}
   562  	return foundUuid == wantedUuid
   563  }
   564  
   565  // GetCatalogById finds a Catalog by ID
   566  // On success, returns a pointer to the Catalog structure and a nil error
   567  // On failure, returns a nil pointer and an error
   568  func (adminOrg *AdminOrg) GetCatalogById(catalogId string, refresh bool) (*Catalog, error) {
   569  	if refresh {
   570  		err := adminOrg.Refresh()
   571  		if err != nil {
   572  			return nil, err
   573  		}
   574  	}
   575  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   576  		if equalIds(catalogId, catalog.ID, catalog.HREF) {
   577  			return adminOrg.GetCatalogByHref(catalog.HREF)
   578  		}
   579  	}
   580  	return nil, ErrorEntityNotFound
   581  }
   582  
   583  // GetCatalogByNameOrId finds a Catalog by name or ID
   584  // On success, returns a pointer to the Catalog structure and a nil error
   585  // On failure, returns a nil pointer and an error
   586  func (adminOrg *AdminOrg) GetCatalogByNameOrId(identifier string, refresh bool) (*Catalog, error) {
   587  	getByName := func(name string, refresh bool) (interface{}, error) { return adminOrg.GetCatalogByName(name, refresh) }
   588  	getById := func(id string, refresh bool) (interface{}, error) { return adminOrg.GetCatalogById(id, refresh) }
   589  	entity, err := getEntityByNameOrId(getByName, getById, identifier, refresh)
   590  	if entity == nil {
   591  		return nil, err
   592  	}
   593  	return entity.(*Catalog), err
   594  }
   595  
   596  // GetAdminCatalogByHref  finds an AdminCatalog by HREF
   597  // On success, returns a pointer to the Catalog structure and a nil error
   598  // On failure, returns a nil pointer and an error
   599  func (adminOrg *AdminOrg) GetAdminCatalogByHref(catalogHref string) (*AdminCatalog, error) {
   600  	adminCatalog := NewAdminCatalog(adminOrg.client)
   601  
   602  	_, err := adminOrg.client.ExecuteRequest(catalogHref, http.MethodGet,
   603  		"", "error retrieving catalog: %s", nil, adminCatalog.AdminCatalog)
   604  
   605  	if err != nil {
   606  		return nil, err
   607  	}
   608  
   609  	adminCatalog.parent = adminOrg
   610  	// The request was successful
   611  	return adminCatalog, nil
   612  }
   613  
   614  // GetCatalogByName finds an AdminCatalog by Name
   615  // On success, returns a pointer to the AdminCatalog structure and a nil error
   616  // On failure, returns a nil pointer and an error
   617  func (adminOrg *AdminOrg) GetAdminCatalogByName(catalogName string, refresh bool) (*AdminCatalog, error) {
   618  	if refresh {
   619  		err := adminOrg.Refresh()
   620  		if err != nil {
   621  			return nil, err
   622  		}
   623  	}
   624  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   625  		// Get Catalog HREF
   626  		if catalog.Name == catalogName {
   627  			return adminOrg.GetAdminCatalogByHref(catalog.HREF)
   628  		}
   629  	}
   630  	return nil, ErrorEntityNotFound
   631  }
   632  
   633  // GetCatalogById finds an AdminCatalog by ID
   634  // On success, returns a pointer to the AdminCatalog structure and a nil error
   635  // On failure, returns a nil pointer and an error
   636  func (adminOrg *AdminOrg) GetAdminCatalogById(catalogId string, refresh bool) (*AdminCatalog, error) {
   637  	if refresh {
   638  		err := adminOrg.Refresh()
   639  		if err != nil {
   640  			return nil, err
   641  		}
   642  	}
   643  	for _, catalog := range adminOrg.AdminOrg.Catalogs.Catalog {
   644  		// Get Catalog HREF
   645  		if equalIds(catalogId, catalog.ID, catalog.HREF) {
   646  			return adminOrg.GetAdminCatalogByHref(catalog.HREF)
   647  		}
   648  	}
   649  	return nil, ErrorEntityNotFound
   650  }
   651  
   652  // GetAdminCatalogByNameOrId finds an AdminCatalog by name or ID
   653  // On success, returns a pointer to the AdminCatalog structure and a nil error
   654  // On failure, returns a nil pointer and an error
   655  func (adminOrg *AdminOrg) GetAdminCatalogByNameOrId(identifier string, refresh bool) (*AdminCatalog, error) {
   656  	getByName := func(name string, refresh bool) (interface{}, error) {
   657  		return adminOrg.GetAdminCatalogByName(name, refresh)
   658  	}
   659  	getById := func(id string, refresh bool) (interface{}, error) {
   660  		return adminOrg.GetAdminCatalogById(id, refresh)
   661  	}
   662  	entity, err := getEntityByNameOrId(getByName, getById, identifier, refresh)
   663  	if entity == nil {
   664  		return nil, err
   665  	}
   666  	return entity.(*AdminCatalog), err
   667  }
   668  
   669  // GetVDCByHref retrieves a VDC using a direct call with the HREF
   670  func (adminOrg *AdminOrg) GetVDCByHref(vdcHref string) (*Vdc, error) {
   671  	splitByAdminHREF := strings.Split(vdcHref, "/api/admin")
   672  
   673  	// admin user and normal user will have different urls
   674  	var vdcHREF string
   675  	if len(splitByAdminHREF) == 1 {
   676  		vdcHREF = vdcHref
   677  	} else {
   678  		vdcHREF = splitByAdminHREF[0] + "/api" + splitByAdminHREF[1]
   679  	}
   680  
   681  	vdc := NewVdc(adminOrg.client)
   682  
   683  	_, err := adminOrg.client.ExecuteRequest(vdcHREF, http.MethodGet,
   684  		"", "error getting vdc: %s", nil, vdc.Vdc)
   685  
   686  	if err != nil {
   687  		return nil, err
   688  	}
   689  	vdc.parent = adminOrg
   690  
   691  	return vdc, nil
   692  }
   693  
   694  // GetVDCByName finds a VDC by Name
   695  // On success, returns a pointer to the Vdc structure and a nil error
   696  // On failure, returns a nil pointer and an error
   697  func (adminOrg *AdminOrg) GetVDCByName(vdcName string, refresh bool) (*Vdc, error) {
   698  	if refresh {
   699  		err := adminOrg.Refresh()
   700  		if err != nil {
   701  			return nil, err
   702  		}
   703  	}
   704  	for _, vdc := range adminOrg.AdminOrg.Vdcs.Vdcs {
   705  		if vdc.Name == vdcName {
   706  			return adminOrg.GetVDCByHref(vdc.HREF)
   707  		}
   708  	}
   709  	return nil, ErrorEntityNotFound
   710  }
   711  
   712  // GetVDCById finds a VDC by ID
   713  // On success, returns a pointer to the Vdc structure and a nil error
   714  // On failure, returns a nil pointer and an error
   715  func (adminOrg *AdminOrg) GetVDCById(vdcId string, refresh bool) (*Vdc, error) {
   716  	if refresh {
   717  		err := adminOrg.Refresh()
   718  		if err != nil {
   719  			return nil, err
   720  		}
   721  	}
   722  	for _, vdc := range adminOrg.AdminOrg.Vdcs.Vdcs {
   723  		if equalIds(vdcId, vdc.ID, vdc.HREF) {
   724  			return adminOrg.GetVDCByHref(vdc.HREF)
   725  		}
   726  	}
   727  	return nil, ErrorEntityNotFound
   728  }
   729  
   730  // GetVDCByNameOrId finds a VDC by name or ID
   731  // On success, returns a pointer to the VDC structure and a nil error
   732  // On failure, returns a nil pointer and an error
   733  func (adminOrg *AdminOrg) GetVDCByNameOrId(identifier string, refresh bool) (*Vdc, error) {
   734  	getByName := func(name string, refresh bool) (interface{}, error) { return adminOrg.GetVDCByName(name, refresh) }
   735  	getById := func(id string, refresh bool) (interface{}, error) { return adminOrg.GetVDCById(id, refresh) }
   736  	entity, err := getEntityByNameOrId(getByName, getById, identifier, refresh)
   737  	if entity == nil {
   738  		return nil, err
   739  	}
   740  	return entity.(*Vdc), err
   741  }
   742  
   743  // If user specifies valid vdc name then this returns a vdc object.
   744  // If no vdc is found, then it returns an empty vdc and no error.
   745  // Otherwise it returns an empty vdc and an error. This function
   746  // allows users to use an AdminOrg to fetch a vdc as well.
   747  // Deprecated: Use adminOrg.GetVDCByName instead
   748  func (adminOrg *AdminOrg) GetVdcByName(vdcname string) (Vdc, error) {
   749  	for _, vdcs := range adminOrg.AdminOrg.Vdcs.Vdcs {
   750  		if vdcs.Name == vdcname {
   751  			splitByAdminHREF := strings.Split(vdcs.HREF, "/api/admin")
   752  
   753  			// admin user and normal user will have different urls
   754  			var vdcHREF string
   755  			if len(splitByAdminHREF) == 1 {
   756  				vdcHREF = vdcs.HREF
   757  			} else {
   758  				vdcHREF = splitByAdminHREF[0] + "/api" + splitByAdminHREF[1]
   759  			}
   760  
   761  			vdc := NewVdc(adminOrg.client)
   762  
   763  			_, err := adminOrg.client.ExecuteRequest(vdcHREF, http.MethodGet,
   764  				"", "error getting vdc: %s", nil, vdc.Vdc)
   765  
   766  			return *vdc, err
   767  		}
   768  	}
   769  	return Vdc{}, nil
   770  }
   771  
   772  // QueryCatalogList returns a list of catalogs for this organization
   773  func (adminOrg *AdminOrg) QueryCatalogList() ([]*types.CatalogRecord, error) {
   774  	return adminOrg.FindCatalogRecords("")
   775  }
   776  
   777  // FindCatalogRecords given a catalog name, retrieves the catalogRecords for a given organization
   778  func (adminOrg *AdminOrg) FindCatalogRecords(name string) ([]*types.CatalogRecord, error) {
   779  	util.Logger.Printf("[DEBUG] QueryCatalogList with org name %s", adminOrg.AdminOrg.Name)
   780  
   781  	var tenantHeaders map[string]string
   782  
   783  	if adminOrg.client.IsSysAdmin {
   784  		// Set tenant context headers just for the query
   785  		tenantHeaders = map[string]string{
   786  			types.HeaderAuthContext:   adminOrg.TenantContext.OrgName,
   787  			types.HeaderTenantContext: adminOrg.TenantContext.OrgId,
   788  		}
   789  	}
   790  
   791  	var filter string
   792  	filter = fmt.Sprintf("orgName==%s", url.QueryEscape(adminOrg.AdminOrg.Name))
   793  	if name != "" {
   794  		filter = fmt.Sprintf("%s;name==%s", filter, url.QueryEscape(name))
   795  	}
   796  
   797  	results, err := adminOrg.client.cumulativeQueryWithHeaders(types.QtCatalog, nil, map[string]string{
   798  		"type":          types.QtCatalog,
   799  		"filter":        filter,
   800  		"filterEncoded": "true",
   801  	}, tenantHeaders)
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  
   806  	catalogs := results.Results.CatalogRecord
   807  	if catalogs == nil {
   808  		return nil, ErrorEntityNotFound
   809  	}
   810  
   811  	util.Logger.Printf("[DEBUG] QueryCatalogList returned with : %#v and error: %s", catalogs, err)
   812  	return catalogs, nil
   813  }