github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/org.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 "errors" 9 "fmt" 10 "net/http" 11 "net/url" 12 "strings" 13 14 "github.com/vmware/go-vcloud-director/v2/types/v56" 15 "github.com/vmware/go-vcloud-director/v2/util" 16 ) 17 18 type Org struct { 19 Org *types.Org 20 client *Client 21 TenantContext *TenantContext 22 } 23 24 func NewOrg(client *Client) *Org { 25 return &Org{ 26 Org: new(types.Org), 27 client: client, 28 } 29 } 30 31 // Given an org with a valid HREF, the function refetches the org 32 // and updates the user's org data. Otherwise if the function fails, 33 // it returns an error. Users should use refresh whenever they have 34 // a stale org due to the creation/update/deletion of a resource 35 // within the org or the org itself. 36 func (org *Org) Refresh() error { 37 if *org == (Org{}) { 38 return fmt.Errorf("cannot refresh, Object is empty") 39 } 40 41 // Empty struct before a new unmarshal, otherwise we end up with duplicate 42 // elements in slices. 43 unmarshalledOrg := &types.Org{} 44 45 _, err := org.client.ExecuteRequest(org.Org.HREF, http.MethodGet, 46 "", "error refreshing organization: %s", nil, unmarshalledOrg) 47 if err != nil { 48 return err 49 } 50 org.Org = unmarshalledOrg 51 52 // The request was successful 53 return nil 54 } 55 56 // Given a valid catalog name, FindCatalog returns a Catalog object. 57 // If no catalog is found, then returns an empty catalog and no error. 58 // Otherwise it returns an error. 59 // Deprecated: use org.GetCatalogByName instead 60 func (org *Org) FindCatalog(catalogName string) (Catalog, error) { 61 62 for _, link := range org.Org.Link { 63 if link.Rel == "down" && link.Type == "application/vnd.vmware.vcloud.catalog+xml" && link.Name == catalogName { 64 65 cat := NewCatalog(org.client) 66 67 _, err := org.client.ExecuteRequest(link.HREF, http.MethodGet, 68 "", "error retrieving catalog: %s", nil, cat.Catalog) 69 70 return *cat, err 71 } 72 } 73 74 return Catalog{}, nil 75 } 76 77 // GetVdcByName if user specifies valid vdc name then this returns a vdc object. 78 // If no vdc is found, then it returns an empty vdc and no error. 79 // Otherwise it returns an empty vdc and an error. 80 // Deprecated: use org.GetVDCByName instead 81 func (org *Org) GetVdcByName(vdcname string) (Vdc, error) { 82 for _, link := range org.Org.Link { 83 if link.Name == vdcname { 84 vdc := NewVdc(org.client) 85 vdc.parent = org 86 87 _, err := org.client.ExecuteRequest(link.HREF, http.MethodGet, 88 "", "error retrieving vdc: %s", nil, vdc.Vdc) 89 90 return *vdc, err 91 } 92 } 93 return Vdc{}, nil 94 } 95 96 // CreateCatalog creates a catalog with specified name and description 97 func CreateCatalog(client *Client, links types.LinkList, Name, Description string) (AdminCatalog, error) { 98 adminCatalog, err := CreateCatalogWithStorageProfile(client, links, Name, Description, nil) 99 if err != nil { 100 return AdminCatalog{}, nil 101 } 102 return *adminCatalog, nil 103 } 104 105 // CreateCatalogWithStorageProfile is like CreateCatalog, but allows to specify storage profile 106 func CreateCatalogWithStorageProfile(client *Client, links types.LinkList, Name, Description string, storageProfiles *types.CatalogStorageProfiles) (*AdminCatalog, error) { 107 reqCatalog := &types.Catalog{ 108 Name: Name, 109 Description: Description, 110 } 111 vcomp := &types.AdminCatalog{ 112 Xmlns: types.XMLNamespaceVCloud, 113 Catalog: *reqCatalog, 114 CatalogStorageProfiles: storageProfiles, 115 } 116 117 var createOrgLink *types.Link 118 for _, link := range links { 119 if link.Rel == "add" && link.Type == types.MimeAdminCatalog { 120 util.Logger.Printf("[TRACE] Create org - found the proper link for request, HREF: %s, "+ 121 "name: %s, type: %s, id: %s, rel: %s \n", link.HREF, link.Name, link.Type, link.ID, link.Rel) 122 createOrgLink = link 123 } 124 } 125 126 if createOrgLink == nil { 127 return nil, fmt.Errorf("creating catalog failed to find url") 128 } 129 130 catalog := NewAdminCatalog(client) 131 _, err := client.ExecuteRequest(createOrgLink.HREF, http.MethodPost, 132 "application/vnd.vmware.admin.catalog+xml", "error creating catalog: %s", vcomp, catalog.AdminCatalog) 133 if err != nil { 134 return nil, err 135 } 136 137 err = catalog.WaitForTasks() 138 if err != nil { 139 return nil, err 140 } 141 return catalog, nil 142 } 143 144 // CreateCatalog creates a catalog with given name and description under 145 // the given organization. Returns an Catalog that contains a creation 146 // task. 147 // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/POST-CreateCatalog.html 148 func (org *Org) CreateCatalog(name, description string) (Catalog, error) { 149 catalog, err := org.CreateCatalogWithStorageProfile(name, description, nil) 150 if err != nil { 151 return Catalog{}, err 152 } 153 catalog.parent = org 154 155 err = catalog.Refresh() 156 if err != nil { 157 return Catalog{}, err 158 } 159 // Make sure that the creation task is finished 160 err = catalog.WaitForTasks() 161 if err != nil { 162 return Catalog{}, err 163 } 164 return *catalog, nil 165 } 166 167 // CreateCatalogWithStorageProfile is like CreateCatalog but additionally allows to specify storage profiles 168 func (org *Org) CreateCatalogWithStorageProfile(name, description string, storageProfiles *types.CatalogStorageProfiles) (*Catalog, error) { 169 catalog := NewCatalog(org.client) 170 adminCatalog, err := CreateCatalogWithStorageProfile(org.client, org.Org.Link, name, description, storageProfiles) 171 if err != nil { 172 return nil, err 173 } 174 catalog.Catalog = &adminCatalog.AdminCatalog.Catalog 175 catalog.parent = org 176 return catalog, nil 177 } 178 179 func validateVdcConfiguration(vdcDefinition *types.VdcConfiguration) error { 180 if vdcDefinition.Name == "" { 181 return errors.New("VdcConfiguration missing required field: Name") 182 } 183 if vdcDefinition.AllocationModel == "" { 184 return errors.New("VdcConfiguration missing required field: AllocationModel") 185 } 186 if vdcDefinition.ComputeCapacity == nil { 187 return errors.New("VdcConfiguration missing required field: ComputeCapacity") 188 } 189 if len(vdcDefinition.ComputeCapacity) != 1 { 190 return errors.New("VdcConfiguration invalid field: ComputeCapacity must only have one element") 191 } 192 if vdcDefinition.ComputeCapacity[0] == nil { 193 return errors.New("VdcConfiguration missing required field: ComputeCapacity[0]") 194 } 195 if vdcDefinition.ComputeCapacity[0].CPU == nil { 196 return errors.New("VdcConfiguration missing required field: ComputeCapacity[0].CPU") 197 } 198 if vdcDefinition.ComputeCapacity[0].CPU.Units == "" { 199 return errors.New("VdcConfiguration missing required field: ComputeCapacity[0].CPU.Units") 200 } 201 if vdcDefinition.ComputeCapacity[0].Memory == nil { 202 return errors.New("VdcConfiguration missing required field: ComputeCapacity[0].Memory") 203 } 204 if vdcDefinition.ComputeCapacity[0].Memory.Units == "" { 205 return errors.New("VdcConfiguration missing required field: ComputeCapacity[0].Memory.Units") 206 } 207 if vdcDefinition.VdcStorageProfile == nil || len(vdcDefinition.VdcStorageProfile) == 0 { 208 return errors.New("VdcConfiguration missing required field: VdcStorageProfile") 209 } 210 if vdcDefinition.VdcStorageProfile[0].Units == "" { 211 return errors.New("VdcConfiguration missing required field: VdcStorageProfile.Units") 212 } 213 if vdcDefinition.ProviderVdcReference == nil { 214 return errors.New("VdcConfiguration missing required field: ProviderVdcReference") 215 } 216 if vdcDefinition.ProviderVdcReference.HREF == "" { 217 return errors.New("VdcConfiguration missing required field: ProviderVdcReference.HREF") 218 } 219 return nil 220 } 221 222 // GetCatalogByHref finds a Catalog by HREF 223 // On success, returns a pointer to the Catalog structure and a nil error 224 // On failure, returns a nil pointer and an error 225 func (org *Org) GetCatalogByHref(catalogHref string) (*Catalog, error) { 226 cat := NewCatalog(org.client) 227 228 _, err := org.client.ExecuteRequest(catalogHref, http.MethodGet, 229 "", "error retrieving catalog: %s", nil, cat.Catalog) 230 if err != nil { 231 return nil, err 232 } 233 // The request was successful 234 cat.parent = org 235 return cat, nil 236 } 237 238 // GetCatalogByName finds a Catalog by Name 239 // On success, returns a pointer to the Catalog structure and a nil error 240 // On failure, returns a nil pointer and an error 241 // 242 // refresh has no effect here, but is kept to preserve signature 243 func (org *Org) GetCatalogByName(catalogName string, refresh bool) (*Catalog, error) { 244 vdcQuery, err := org.queryCatalogByName(catalogName) 245 if ContainsNotFound(err) { 246 return nil, ErrorEntityNotFound 247 } 248 if err != nil { 249 return nil, fmt.Errorf("error querying Catalog: %s", err) 250 } 251 // This is not an AdminOrg and admin HREF must be removed if it exists 252 href := strings.Replace(vdcQuery.HREF, "/api/admin", "/api", 1) 253 return org.GetCatalogByHref(href) 254 } 255 256 // GetCatalogById finds a Catalog by ID 257 // On success, returns a pointer to the Catalog structure and a nil error 258 // On failure, returns a nil pointer and an error 259 func (org *Org) GetCatalogById(catalogId string, refresh bool) (*Catalog, error) { 260 vdcQuery, err := org.queryCatalogById(catalogId) 261 if ContainsNotFound(err) { 262 return nil, ErrorEntityNotFound 263 } 264 if err != nil { 265 return nil, fmt.Errorf("error querying Catalog: %s", err) 266 } 267 268 // This is not an AdminOrg and admin HREF must be removed if it exists 269 href := strings.Replace(vdcQuery.HREF, "/api/admin", "/api", 1) 270 return org.GetCatalogByHref(href) 271 } 272 273 // GetCatalogByNameOrId finds a Catalog by name or ID 274 // On success, returns a pointer to the Catalog structure and a nil error 275 // On failure, returns a nil pointer and an error 276 func (org *Org) GetCatalogByNameOrId(identifier string, refresh bool) (*Catalog, error) { 277 getByName := func(name string, refresh bool) (interface{}, error) { 278 return org.GetCatalogByName(name, refresh) 279 } 280 getById := func(id string, refresh bool) (interface{}, error) { 281 return org.GetCatalogById(id, refresh) 282 } 283 entity, err := getEntityByNameOrIdSkipNonId(getByName, getById, identifier, refresh) 284 if entity == nil { 285 return nil, err 286 } 287 return entity.(*Catalog), err 288 } 289 290 // GetVDCByHref finds a VDC by HREF 291 // On success, returns a pointer to the VDC structure and a nil error 292 // On failure, returns a nil pointer and an error 293 func (org *Org) GetVDCByHref(vdcHref string) (*Vdc, error) { 294 vdc := NewVdc(org.client) 295 _, err := org.client.ExecuteRequest(vdcHref, http.MethodGet, 296 "", "error retrieving VDC: %s", nil, vdc.Vdc) 297 if err != nil { 298 return nil, err 299 } 300 // The request was successful 301 vdc.parent = org 302 return vdc, nil 303 } 304 305 // GetVDCByName finds a VDC by Name 306 // On success, returns a pointer to the VDC structure and a nil error 307 // On failure, returns a nil pointer and an error 308 // 309 // refresh has no effect and is kept to preserve signature 310 func (org *Org) GetVDCByName(vdcName string, refresh bool) (*Vdc, error) { 311 vdcQuery, err := org.queryOrgVdcByName(vdcName) 312 if ContainsNotFound(err) { 313 return nil, ErrorEntityNotFound 314 } 315 if err != nil { 316 return nil, fmt.Errorf("error querying VDC: %s", err) 317 } 318 // This is not an AdminOrg and admin HREF must be removed if it exists 319 href := strings.Replace(vdcQuery.HREF, "/api/admin", "/api", 1) 320 return org.GetVDCByHref(href) 321 } 322 323 // GetVDCById finds a VDC by ID 324 // On success, returns a pointer to the VDC structure and a nil error 325 // On failure, returns a nil pointer and an error 326 // 327 // refresh has no effect and is kept to preserve signature 328 func (org *Org) GetVDCById(vdcId string, refresh bool) (*Vdc, error) { 329 vdcQuery, err := org.queryOrgVdcById(vdcId) 330 if ContainsNotFound(err) { 331 return nil, ErrorEntityNotFound 332 } 333 if err != nil { 334 return nil, fmt.Errorf("error querying VDC: %s", err) 335 } 336 337 // This is not an AdminOrg and admin HREF must be removed if it exists 338 href := strings.Replace(vdcQuery.HREF, "/api/admin", "/api", 1) 339 return org.GetVDCByHref(href) 340 } 341 342 // GetVDCByNameOrId finds a VDC by name or ID 343 // On success, returns a pointer to the VDC structure and a nil error 344 // On failure, returns a nil pointer and an error 345 // 346 // refresh has no effect and is kept to preserve signature 347 func (org *Org) GetVDCByNameOrId(identifier string, refresh bool) (*Vdc, error) { 348 getByName := func(name string, refresh bool) (interface{}, error) { 349 return org.GetVDCByName(name, refresh) 350 } 351 getById := func(id string, refresh bool) (interface{}, error) { 352 return org.GetVDCById(id, refresh) 353 } 354 entity, err := getEntityByNameOrIdSkipNonId(getByName, getById, identifier, refresh) 355 if entity == nil { 356 return nil, err 357 } 358 return entity.(*Vdc), err 359 } 360 361 // QueryCatalogList returns a list of catalogs for this organization 362 func (org *Org) QueryCatalogList() ([]*types.CatalogRecord, error) { 363 util.Logger.Printf("[DEBUG] QueryCatalogList with Org HREF %s", org.Org.HREF) 364 filter := map[string]string{ 365 "org": org.Org.HREF, 366 } 367 return queryCatalogList(org.client, filter) 368 } 369 370 // GetTaskList returns Tasks for Organization and error. 371 func (org *Org) GetTaskList() (*types.TasksList, error) { 372 373 for _, link := range org.Org.Link { 374 if link.Rel == "down" && link.Type == "application/vnd.vmware.vcloud.tasksList+xml" { 375 376 tasksList := &types.TasksList{} 377 378 _, err := org.client.ExecuteRequest(link.HREF, http.MethodGet, "", 379 "error getting taskList: %s", nil, tasksList) 380 if err != nil { 381 return nil, err 382 } 383 384 return tasksList, nil 385 } 386 } 387 388 return nil, fmt.Errorf("link not found") 389 } 390 391 // queryOrgVdcByName returns a single QueryResultOrgVdcRecordType 392 func (org *Org) queryOrgVdcByName(vdcName string) (*types.QueryResultOrgVdcRecordType, error) { 393 filterFields := map[string]string{ 394 "org": org.Org.HREF, 395 "orgName": org.Org.Name, 396 "name": vdcName, 397 } 398 allVdcs, err := queryOrgVdcList(org.client, filterFields) 399 if err != nil { 400 return nil, err 401 } 402 403 if allVdcs == nil || len(allVdcs) < 1 { 404 return nil, ErrorEntityNotFound 405 } 406 407 if len(allVdcs) > 1 { 408 return nil, fmt.Errorf("found more than 1 VDC with Name '%s'", vdcName) 409 } 410 411 return allVdcs[0], nil 412 } 413 414 // queryOrgVdcById returns a single QueryResultOrgVdcRecordType 415 func (org *Org) queryOrgVdcById(vdcId string) (*types.QueryResultOrgVdcRecordType, error) { 416 filterMap := map[string]string{ 417 "org": org.Org.HREF, 418 "orgName": org.Org.Name, 419 "id": vdcId, 420 } 421 allVdcs, err := queryOrgVdcList(org.client, filterMap) 422 423 if err != nil { 424 return nil, err 425 } 426 427 if len(allVdcs) < 1 { 428 return nil, ErrorEntityNotFound 429 } 430 431 return allVdcs[0], nil 432 } 433 434 // queryCatalogByName returns a single CatalogRecord 435 func (org *Org) queryCatalogByName(catalogName string) (*types.CatalogRecord, error) { 436 filterMap := map[string]string{ 437 // Not injecting `org` or `orgName` here because shared catalogs may also appear here and they would have different 438 // parent Org 439 // "org": org.Org.HREF, 440 // "orgName": org.Org.Name, 441 "name": catalogName, 442 } 443 allCatalogs, err := queryCatalogList(org.client, filterMap) 444 if err != nil { 445 return nil, err 446 } 447 448 if allCatalogs == nil || len(allCatalogs) < 1 { 449 return nil, ErrorEntityNotFound 450 } 451 452 // To conform with this API standard it would be best to return an error if more than 1 item is found, but because 453 // previous method of getting Catalog by Name returned the first result we are doing the same here 454 // if len(allCatalogs) > 1 { 455 // return nil, fmt.Errorf("found more than 1 Catalog with Name '%s'", catalogName) 456 // } 457 458 var localCatalog *types.CatalogRecord 459 // if multiple results are found - return the one defined in `org` (local) 460 if len(allCatalogs) > 1 { 461 util.Logger.Printf("[DEBUG] org.queryCatalogByName found %d Catalogs by name '%s'", len(allCatalogs), catalogName) 462 for _, catalog := range allCatalogs { 463 util.Logger.Printf("[DEBUG] org.queryCatalogByName found a Catalog by name '%s' in Org '%s'", catalogName, catalog.OrgName) 464 if catalog.OrgName == org.Org.Name { 465 util.Logger.Printf("[DEBUG] org.queryCatalogByName Catalog '%s' is local for Org '%s'. Prioritising it", 466 catalogName, org.Org.Name) 467 // Not interrupting the loop here to still dump all results to logs 468 localCatalog = catalog 469 } 470 } 471 } 472 473 // local catalog was found - return it 474 if localCatalog != nil { 475 return localCatalog, nil 476 } 477 478 // If only one catalog is found or multiple catalogs with no local ones - return the first one 479 return allCatalogs[0], nil 480 } 481 482 // queryCatalogById returns a single QueryResultOrgVdcRecordType 483 func (org *Org) queryCatalogById(catalogId string) (*types.CatalogRecord, error) { 484 filterMap := map[string]string{ 485 // Not injecting `org` or `orgName` here because shared catalogs may also appear here and they would have different 486 // parent Org 487 // "org": org.Org.HREF, 488 // "orgName": org.Org.Name, 489 "id": catalogId, 490 } 491 allCatalogs, err := queryCatalogList(org.client, filterMap) 492 493 if err != nil { 494 return nil, err 495 } 496 497 if len(allCatalogs) < 1 { 498 return nil, ErrorEntityNotFound 499 } 500 501 return allCatalogs[0], nil 502 } 503 504 // QueryOrgVdcList returns all Org VDCs using query endpoint 505 // 506 // Note. Being a 'System' user it will not return any VDC 507 func (org *Org) QueryOrgVdcList() ([]*types.QueryResultOrgVdcRecordType, error) { 508 filter := map[string]string{ 509 "org": org.Org.HREF, 510 } 511 512 return queryOrgVdcList(org.client, filter) 513 } 514 515 // queryOrgVdcList performs an `orgVdc` or `adminOrgVdc` (for System user) and optionally applies filterFields 516 func queryOrgVdcList(client *Client, filterFields map[string]string) ([]*types.QueryResultOrgVdcRecordType, error) { 517 util.Logger.Printf("[DEBUG] queryOrgVdcList with filter %#v", filterFields) 518 queryType := client.GetQueryType(types.QtOrgVdc) 519 520 filter := map[string]string{ 521 "type": queryType, 522 } 523 524 // When a map of filters with non empty keys and values is supplied - apply it 525 if filterFields != nil { 526 filterSlice := make([]string, 0) 527 528 for filterFieldName, filterFieldValue := range filterFields { 529 // Do not inject 'org' filter for System user as API returns an error 530 if !client.IsSysAdmin && filterFieldName == "org" { 531 continue 532 } 533 534 if filterFieldName != "" && filterFieldValue != "" { 535 filterText := fmt.Sprintf("%s==%s", filterFieldName, url.QueryEscape(filterFieldValue)) 536 filterSlice = append(filterSlice, filterText) 537 } 538 } 539 540 if len(filterSlice) > 0 { 541 filter["filter"] = strings.Join(filterSlice, ";") 542 filter["filterEncoded"] = "true" 543 } 544 } 545 546 results, err := client.cumulativeQuery(queryType, nil, filter) 547 if err != nil { 548 return nil, fmt.Errorf("error querying Org VDCs %s", err) 549 } 550 551 if client.IsSysAdmin { 552 return results.Results.OrgVdcAdminRecord, nil 553 } else { 554 return results.Results.OrgVdcRecord, nil 555 } 556 } 557 558 func queryCatalogList(client *Client, filterFields map[string]string) ([]*types.CatalogRecord, error) { 559 util.Logger.Printf("[DEBUG] queryCatalogList with filter %#v", filterFields) 560 queryType := client.GetQueryType(types.QtCatalog) 561 562 filter := map[string]string{ 563 "type": queryType, 564 } 565 566 // When a map of filters with non empty keys and values is supplied - apply it 567 if filterFields != nil { 568 filterSlice := make([]string, 0) 569 570 for filterFieldName, filterFieldValue := range filterFields { 571 // Do not inject 'org' filter for System user as API returns an error 572 if !client.IsSysAdmin && filterFieldName == "org" { 573 continue 574 } 575 576 if filterFieldName != "" && filterFieldValue != "" { 577 filterText := fmt.Sprintf("%s==%s", filterFieldName, url.QueryEscape(filterFieldValue)) 578 filterSlice = append(filterSlice, filterText) 579 } 580 } 581 582 if len(filterSlice) > 0 { 583 filter["filter"] = strings.Join(filterSlice, ";") 584 filter["filterEncoded"] = "true" 585 } 586 } 587 588 results, err := client.cumulativeQuery(queryType, nil, filter) 589 if err != nil { 590 return nil, err 591 } 592 593 var catalogs []*types.CatalogRecord 594 595 if client.IsSysAdmin { 596 catalogs = results.Results.AdminCatalogRecord 597 } else { 598 catalogs = results.Results.CatalogRecord 599 } 600 util.Logger.Printf("[DEBUG] QueryCatalogList returned with : %#v and error: %s", catalogs, err) 601 return catalogs, nil 602 } 603 604 // GetVappByHref returns a vApp reference by running a VCD API call 605 // If no valid vApp is found, it returns a nil VApp reference and an error 606 func (org *Org) GetVAppByHref(vappHref string) (*VApp, error) { 607 newVapp := NewVApp(org.client) 608 609 _, err := org.client.ExecuteRequest(vappHref, http.MethodGet, 610 "", "error retrieving vApp: %s", nil, newVapp.VApp) 611 612 if err != nil { 613 return nil, err 614 } 615 return newVapp, nil 616 }