diff --git a/CHANGELOG.md b/CHANGELOG.md index 18bcdfd9a..e4e6b6880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ * Improved testing tags isolation [#320](https://github.com/vmware/go-vcloud-director/pull/320) * Added command `make tagverify` to check tags isolation tests [#320](https://github.com/vmware/go-vcloud-director/pull/320) +* Added methods `Client.GetAccessControl`, `Client.SetAccessControl`[#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added methods `VApp.GetAccessControl`, `VApp.SetAccessControl`, `VApp.RemoveAccessControl`, `VApp.IsShared` [#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added methods `AdminCatalog.GetAccessControl`, `AdminCatalog.SetAccessControl`, `AdminCatalog.RemoveAccessControl`, `AdminCatalog.IsShared` [#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added methods `Catalog.GetAccessControl`, `Catalog.SetAccessControl`, `Catalog.RemoveAccessControl`, `Catalog.IsShared` [#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added methods `Vdc.GetVappAccessControl`, `AdminOrg.GetCatalogAccessControl`, `Org.GetCatalogAccessControl` [#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added methods `Vdc.QueryVappList`, `Vdc.GetVappList`, `AdminVdc.GetVappList`, `client.GetQueryType` [#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added VM and vApp to search query engine [#329](https://github.com/vmware/go-vcloud-director/pull/329) +* Added tenant context for access control methods [#329](https://github.com/vmware/go-vcloud-director/pull/329) * Loosen up `Test_LBAppRule` for invalid application script check to work with different error engine in VCD 10.2 [#326](https://github.com/vmware/go-vcloud-director/pull/326) * Update VDC dynamic func to handle API version 35.0 [#327](https://github.com/vmware/go-vcloud-director/pull/327) diff --git a/govcd/access_control.go b/govcd/access_control.go new file mode 100644 index 000000000..e83dff108 --- /dev/null +++ b/govcd/access_control.go @@ -0,0 +1,362 @@ +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "bytes" + "encoding/xml" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// orgInfoType is the basic information about an organization (needed for tenant context) +type orgInfoType struct { + id string + name string +} + +// orgInfoCache is a cache to save org information, avoid repeated calls to compute the same result. +// The keys to this map are the requesting objects IDs. +var orgInfoCache = make(map[string]orgInfoType) + +// GetAccessControl retrieves the access control information for the requested entity +func (client Client) GetAccessControl(href, entityType, entityName string, headerValues map[string]string) (*types.ControlAccessParams, error) { + + href += "/controlAccess" + var controlAccess types.ControlAccessParams + + acUrl, err := url.ParseRequestURI(href) + if err != nil { + return nil, fmt.Errorf("[client.GetAccessControl] error parsing HREF %s: %s", href, err) + } + var additionalHeader = make(http.Header) + + if len(headerValues) > 0 { + for k, v := range headerValues { + additionalHeader.Add(k, v) + } + } + req := client.newRequest( + nil, // params + nil, // notEncodedParams + http.MethodGet, // method + *acUrl, // reqUrl + nil, // body + client.APIVersion, // apiVersion + additionalHeader, // additionalHeader + ) + + resp, err := checkResp(client.Http.Do(req)) + if err != nil { + return nil, fmt.Errorf("[client.GetAccessControl] error checking response to request %s: %s", href, err) + } + if resp == nil { + return nil, fmt.Errorf("[client.GetAccessControl] nil response received") + } + if err = decodeBody(types.BodyTypeXML, resp, &controlAccess); err != nil { + return nil, fmt.Errorf("[client.GetAccessControl] error decoding response: %s", err) + } + + return &controlAccess, nil +} + +// SetAccessControl changes the access control information for this entity +// There are two ways of setting the access: +// with accessControl.IsSharedToEveryone = true we give access to everyone +// with accessControl.IsSharedToEveryone = false, accessControl.AccessSettings defines which subjects can access the vApp +// For each setting we must provide: +// * The subject (HREF and Type are mandatory) +// * The access level (one of ReadOnly, Change, FullControl) +func (client *Client) SetAccessControl(accessControl *types.ControlAccessParams, href, entityType, entityName string, headerValues map[string]string) error { + + href += "/action/controlAccess" + // Make sure that subjects in the setting list are used only once + if accessControl.AccessSettings != nil && len(accessControl.AccessSettings.AccessSetting) > 0 { + if accessControl.IsSharedToEveryone { + return fmt.Errorf("[client.SetAccessControl] can't set IsSharedToEveryone and AccessSettings at the same time for %s %s (%s)", entityType, entityName, href) + } + var used = make(map[string]bool) + for _, setting := range accessControl.AccessSettings.AccessSetting { + _, seen := used[setting.Subject.HREF] + if seen { + return fmt.Errorf("[client.SetAccessControl] subject %s (%s) used more than once", setting.Subject.Name, setting.Subject.HREF) + } + used[setting.Subject.HREF] = true + if setting.Subject.Type == "" { + return fmt.Errorf("[client.SetAccessControl] subject %s (%s) has no type defined", setting.Subject.Name, setting.Subject.HREF) + } + } + } + + accessControl.Xmlns = types.XMLNamespaceVCloud + queryUrl, err := url.ParseRequestURI(href) + if err != nil { + return fmt.Errorf("[client.SetAccessControl] error parsing HREF %s: %s", href, err) + } + + var header = make(http.Header) + if len(headerValues) > 0 { + for k, v := range headerValues { + header.Add(k, v) + } + } + + marshaledXml, err := xml.MarshalIndent(accessControl, " ", " ") + if err != nil { + return fmt.Errorf("[client.SetAccessControl] error marshalling xml data: %s", err) + } + body := bytes.NewBufferString(xml.Header + string(marshaledXml)) + + req := client.newRequest( + nil, // params + nil, // notEncodedParams + http.MethodPost, // method + *queryUrl, // reqUrl + body, // body + client.APIVersion, // apiVersion + header, // additionalHeader + ) + + resp, err := checkResp(client.Http.Do(req)) + + if err != nil { + return fmt.Errorf("[client.SetAccessControl] error checking response to HREF %s: %s", href, err) + } + if resp == nil { + return fmt.Errorf("[client.SetAccessControl] nil response received") + } + _, err = checkResp(resp, err) + return err +} + +// GetAccessControl retrieves the access control information for this vApp +func (vapp VApp) GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) { + + if vapp.VApp.HREF == "" { + return nil, fmt.Errorf("vApp HREF is empty") + } + // if useTenantContext is false, we use an empty header (= default behavior) + // if it is true, we use a header populated with tenant context values + accessControlHeader, err := vapp.getAccessControlHeader(useTenantContext) + if err != nil { + return nil, err + } + return vapp.client.GetAccessControl(vapp.VApp.HREF, "vApp", vapp.VApp.Name, accessControlHeader) +} + +// SetAccessControl changes the access control information for this vApp +func (vapp VApp) SetAccessControl(accessControl *types.ControlAccessParams, useTenantContext bool) error { + + if vapp.VApp.HREF == "" { + return fmt.Errorf("vApp HREF is empty") + } + + // if useTenantContext is false, we use an empty header (= default behavior) + // if it is true, we use a header populated with tenant context values + accessControlHeader, err := vapp.getAccessControlHeader(useTenantContext) + if err != nil { + return err + } + return vapp.client.SetAccessControl(accessControl, vapp.VApp.HREF, "vApp", vapp.VApp.Name, accessControlHeader) + +} + +// RemoveAccessControl is a shortcut to SetAccessControl with all access disabled +func (vapp VApp) RemoveAccessControl(useTenantContext bool) error { + return vapp.SetAccessControl(&types.ControlAccessParams{IsSharedToEveryone: false}, useTenantContext) +} + +// IsShared shows whether a vApp is shared or not, regardless of the number of subjects sharing it +func (vapp VApp) IsShared(useTenantContext bool) (bool, error) { + settings, err := vapp.GetAccessControl(useTenantContext) + if err != nil { + return false, err + } + if settings.IsSharedToEveryone { + return true, nil + } + return settings.AccessSettings != nil, nil +} + +// GetAccessControl retrieves the access control information for this catalog +func (adminCatalog AdminCatalog) GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) { + + if adminCatalog.AdminCatalog.HREF == "" { + return nil, fmt.Errorf("catalog HREF is empty") + } + href := strings.Replace(adminCatalog.AdminCatalog.HREF, "/admin/", "/", 1) + + // if useTenantContext is false, we use an empty header (= default behavior) + // if it is true, we use a header populated with tenant context values + accessControlHeader, err := adminCatalog.getAccessControlHeader(useTenantContext) + if err != nil { + return nil, err + } + return adminCatalog.client.GetAccessControl(href, "catalog", adminCatalog.AdminCatalog.Name, accessControlHeader) +} + +// SetAccessControl changes the access control information for this catalog +func (adminCatalog AdminCatalog) SetAccessControl(accessControl *types.ControlAccessParams, useTenantContext bool) error { + + if adminCatalog.AdminCatalog.HREF == "" { + return fmt.Errorf("catalog HREF is empty") + } + href := strings.Replace(adminCatalog.AdminCatalog.HREF, "/admin/", "/", 1) + + // if useTenantContext is false, we use an empty header (= default behavior) + // if it is true, we use a header populated with tenant context values + accessControlHeader, err := adminCatalog.getAccessControlHeader(useTenantContext) + if err != nil { + return err + } + return adminCatalog.client.SetAccessControl(accessControl, href, "catalog", adminCatalog.AdminCatalog.Name, accessControlHeader) +} + +// RemoveAccessControl is a shortcut to SetAccessControl with all access disabled +func (adminCatalog AdminCatalog) RemoveAccessControl(useTenantContext bool) error { + return adminCatalog.SetAccessControl(&types.ControlAccessParams{IsSharedToEveryone: false}, useTenantContext) +} + +// IsShared shows whether a catalog is shared or not, regardless of the number of subjects sharing it +func (adminCatalog AdminCatalog) IsShared(useTenantContext bool) (bool, error) { + settings, err := adminCatalog.GetAccessControl(useTenantContext) + if err != nil { + return false, err + } + if settings.IsSharedToEveryone { + return true, nil + } + return settings.AccessSettings != nil, nil +} + +// GetVappAccessControl is a convenience method to retrieve access control for a vApp +// from a VDC. +// The input variable vappIdentifier can be either the vApp name or its ID +func (vdc *Vdc) GetVappAccessControl(vappIdentifier string, useTenantContext bool) (*types.ControlAccessParams, error) { + vapp, err := vdc.GetVAppByNameOrId(vappIdentifier, true) + if err != nil { + return nil, fmt.Errorf("error retrieving vApp %s: %s", vappIdentifier, err) + } + return vapp.GetAccessControl(useTenantContext) +} + +// GetCatalogAccessControl is a convenience method to retrieve access control for a catalog +// from an organization. +// The input variable catalogIdentifier can be either the catalog name or its ID +func (org *AdminOrg) GetCatalogAccessControl(catalogIdentifier string, useTenantContext bool) (*types.ControlAccessParams, error) { + catalog, err := org.GetAdminCatalogByNameOrId(catalogIdentifier, true) + if err != nil { + return nil, fmt.Errorf("error retrieving catalog %s: %s", catalogIdentifier, err) + } + return catalog.GetAccessControl(useTenantContext) +} + +// GetCatalogAccessControl is a convenience method to retrieve access control for a catalog +// from an organization. +// The input variable catalogIdentifier can be either the catalog name or its ID +func (org *Org) GetCatalogAccessControl(catalogIdentifier string, useTenantContext bool) (*types.ControlAccessParams, error) { + catalog, err := org.GetCatalogByNameOrId(catalogIdentifier, true) + if err != nil { + return nil, fmt.Errorf("error retrieving catalog %s: %s", catalogIdentifier, err) + } + return catalog.GetAccessControl(useTenantContext) +} + +// GetAccessControl retrieves the access control information for this catalog +func (catalog Catalog) GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) { + + if catalog.Catalog.HREF == "" { + return nil, fmt.Errorf("catalog HREF is empty") + } + href := strings.Replace(catalog.Catalog.HREF, "/admin/", "/", 1) + accessControlHeader, err := catalog.getAccessControlHeader(useTenantContext) + if err != nil { + return nil, err + } + return catalog.client.GetAccessControl(href, "catalog", catalog.Catalog.Name, accessControlHeader) +} + +// SetAccessControl changes the access control information for this catalog +func (catalog Catalog) SetAccessControl(accessControl *types.ControlAccessParams, useTenantContext bool) error { + + if catalog.Catalog.HREF == "" { + return fmt.Errorf("catalog HREF is empty") + } + + href := strings.Replace(catalog.Catalog.HREF, "/admin/", "/", 1) + + // if useTenantContext is false, we use an empty header (= default behavior) + // if it is true, we use a header populated with tenant context values + accessControlHeader, err := catalog.getAccessControlHeader(useTenantContext) + if err != nil { + return err + } + return catalog.client.SetAccessControl(accessControl, href, "catalog", catalog.Catalog.Name, accessControlHeader) +} + +// RemoveAccessControl is a shortcut to SetAccessControl with all access disabled +func (catalog Catalog) RemoveAccessControl(useTenantContext bool) error { + return catalog.SetAccessControl(&types.ControlAccessParams{IsSharedToEveryone: false}, useTenantContext) +} + +// IsShared shows whether a catalog is shared or not, regardless of the number of subjects sharing it +func (catalog Catalog) IsShared(useTenantContext bool) (bool, error) { + settings, err := catalog.GetAccessControl(useTenantContext) + if err != nil { + return false, err + } + if settings.IsSharedToEveryone { + return true, nil + } + return settings.AccessSettings != nil, nil +} + +// getAccessControlHeader builds the data needed to set the header when tenant context is required. +// If useTenantContext is false, it returns an empty map. +// Otherwise, it finds the Org ID and name (going up in the hierarchy through the VDC) +// and creates the header data +func (vapp *VApp) getAccessControlHeader(useTenantContext bool) (map[string]string, error) { + if !useTenantContext { + return map[string]string{}, nil + } + orgInfo, err := vapp.getOrgInfo() + if err != nil { + return nil, err + } + return map[string]string{types.HeaderTenantContext: orgInfo.id, types.HeaderAuthContext: orgInfo.name}, nil +} + +// getAccessControlHeader builds the data needed to set the header when tenant context is required. +// If useTenantContext is false, it returns an empty map. +// Otherwise, it finds the Org ID and name and creates the header data +func (catalog *Catalog) getAccessControlHeader(useTenantContext bool) (map[string]string, error) { + if !useTenantContext { + return map[string]string{}, nil + } + orgInfo, err := catalog.getOrgInfo() + if err != nil { + return nil, err + } + return map[string]string{types.HeaderTenantContext: orgInfo.id, types.HeaderAuthContext: orgInfo.name}, nil +} + +// getAccessControlHeader builds the data needed to set the header when tenant context is required. +// If useTenantContext is false, it returns an empty map. +// Otherwise, it finds the Org ID and name and creates the header data +func (adminCatalog *AdminCatalog) getAccessControlHeader(useTenantContext bool) (map[string]string, error) { + if !useTenantContext { + return map[string]string{}, nil + } + orgInfo, err := adminCatalog.getOrgInfo() + + if err != nil { + return nil, err + } + return map[string]string{types.HeaderTenantContext: orgInfo.id, types.HeaderAuthContext: orgInfo.name}, nil +} diff --git a/govcd/access_control_catalog_test.go b/govcd/access_control_catalog_test.go new file mode 100644 index 000000000..12191dd1b --- /dev/null +++ b/govcd/access_control_catalog_test.go @@ -0,0 +1,362 @@ +// +build functional catalog ALL + +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "fmt" + "os" + + . "gopkg.in/check.v1" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// catalogTenantContext defines whether we use tenant context during catalog tests. +// By default is off, unless variable VCD_CATALOG_TENANT_CONTEXT is set +var catalogTenantContext = os.Getenv("VCD_CATALOG_TENANT_CONTEXT") != "" + +// GetId completes the implementation of interface accessControlType +func (catalog Catalog) GetId() string { + return catalog.Catalog.ID +} + +// GetId completes the implementation of interface accessControlType +func (catalog AdminCatalog) GetId() string { + return catalog.AdminCatalog.ID +} + +func (vcd *TestVCD) Test_AdminCatalogAccessControl(check *C) { + if vcd.config.VCD.Org == "" { + check.Skip("Test_AdminCatalogAccessControl: Org name not given.") + return + } + org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + check.Assert(org, NotNil) + adminorg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + + check.Assert(err, IsNil) + check.Assert(org, NotNil) + catalogName := "ac-admin-catalog" + // Create a new catalog + adminCatalog, err := adminorg.CreateCatalog(catalogName, catalogName) + check.Assert(err, IsNil) + check.Assert(adminCatalog, NotNil) + AddToCleanupList(catalogName, "catalog", vcd.config.VCD.Org, check.TestName()) + vcd.testCatalogAccessControl(adminorg, adminCatalog, check.TestName(), catalogName, check) + + orgInfo, err := adminCatalog.getOrgInfo() + check.Assert(err, IsNil) + check.Assert(orgInfo.id, Equals, extractUuid(adminorg.AdminOrg.ID)) + check.Assert(orgInfo.name, Equals, adminorg.AdminOrg.Name) + + err = adminCatalog.Delete(true, true) + check.Assert(err, IsNil) +} + +func (vcd *TestVCD) Test_CatalogAccessControl(check *C) { + if vcd.config.VCD.Org == "" { + check.Skip("Test_CatalogAccessControl: Org name not given.") + return + } + org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + check.Assert(org, NotNil) + adminorg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + + check.Assert(err, IsNil) + check.Assert(org, NotNil) + catalogName := "ac-catalog" + // Create a new catalog + adminCatalog, err := adminorg.CreateCatalog(catalogName, catalogName) + check.Assert(err, IsNil) + check.Assert(adminCatalog, NotNil) + AddToCleanupList(catalogName, "catalog", vcd.config.VCD.Org, check.TestName()) + catalog, err := org.GetCatalogByName(catalogName, true) + check.Assert(err, IsNil) + vcd.testCatalogAccessControl(adminorg, catalog, check.TestName(), catalogName, check) + + orgInfo, err := catalog.getOrgInfo() + check.Assert(err, IsNil) + check.Assert(orgInfo.id, Equals, extractUuid(adminorg.AdminOrg.ID)) + check.Assert(orgInfo.name, Equals, adminorg.AdminOrg.Name) + + err = catalog.Delete(true, true) + check.Assert(err, IsNil) +} + +func (vcd *TestVCD) testCatalogAccessControl(adminOrg *AdminOrg, catalog accessControlType, testName, catalogName string, check *C) { + + var users = []struct { + name string + role string + user *OrgUser + }{ + {"ac-user1", OrgUserRoleVappAuthor, nil}, + {"ac-user2", OrgUserRoleOrganizationAdministrator, nil}, + {"ac-user3", OrgUserRoleCatalogAuthor, nil}, + } + + orgName := "ac-testorg" + var newOrg *AdminOrg + var err error + if vcd.client.Client.IsSysAdmin { + + // Create a new Org + task, err := CreateOrg(vcd.client, orgName, orgName, orgName, &types.OrgSettings{}, true) + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + newOrg, err = vcd.client.GetAdminOrgByName(orgName) + check.Assert(err, IsNil) + AddToCleanupList(orgName, "org", "", testName) + defer func() { + if testVerbose { + fmt.Printf("deleting %s\n", orgName) + } + err = newOrg.Disable() + check.Assert(err, IsNil) + err = newOrg.Delete(true, true) + check.Assert(err, IsNil) + }() + } + + checkEmpty := func() { + settings, err := catalog.GetAccessControl(catalogTenantContext) + check.Assert(err, IsNil) + check.Assert(settings.IsSharedToEveryone, Equals, false) // There should not be a global sharing + check.Assert(settings.AccessSettings, IsNil) // There should not be any explicit sharing + } + + // Create three users + for i := 0; i < len(users); i++ { + users[i].user, err = adminOrg.CreateUserSimple(OrgUserConfiguration{ + Name: users[i].name, Password: users[i].name, RoleName: users[i].role, IsEnabled: true, + }) + check.Assert(err, IsNil) + check.Assert(users[i].user, NotNil) + AddToCleanupList(users[i].name, "user", vcd.config.VCD.Org, testName) + } + defer func() { + for i := 0; i < len(users); i++ { + if testVerbose { + fmt.Printf("deleting %s\n", users[i].name) + } + err = users[i].user.Delete(false) + check.Assert(err, IsNil) + } + }() + checkEmpty() + //globalSettings := types.ControlAccessParams{ + // IsSharedToEveryone: true, + // EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadWrite), + // AccessSettings: nil, + //} + //err = testAccessControl(catalogName+" catalog global", catalog, globalSettings, globalSettings, true, check) + //check.Assert(err, IsNil) + + // Set control access to one user + oneUserSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + Name: users[0].user.User.Name, + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadWrite, + }, + }, + }, + } + err = testAccessControl(catalogName+" catalog one user", catalog, oneUserSettings, oneUserSettings, true, catalogTenantContext, check) + check.Assert(err, IsNil) + + // Check that vapp.GetAccessControl and vdc.GetVappAccessControl return the same data + controlAccess, err := catalog.GetAccessControl(catalogTenantContext) + check.Assert(err, IsNil) + orgControlAccessByName, err := adminOrg.GetCatalogAccessControl(catalogName, catalogTenantContext) + check.Assert(err, IsNil) + check.Assert(controlAccess, DeepEquals, orgControlAccessByName) + + orgControlAccessById, err := adminOrg.GetCatalogAccessControl(catalog.GetId(), catalogTenantContext) + check.Assert(err, IsNil) + check.Assert(controlAccess, DeepEquals, orgControlAccessById) + + // Set control access to two users + twoUserSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + //Name: users[0].user.User.Name, // Pass info without name for one of the subjects + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[1].user.User.Href, + Name: users[1].user.User.Name, + Type: users[1].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessFullControl, + }, + }, + }, + } + err = testAccessControl(catalogName+" catalog two users", catalog, twoUserSettings, twoUserSettings, true, catalogTenantContext, check) + check.Assert(err, IsNil) + + // Check removal of sharing setting + err = catalog.RemoveAccessControl(catalogTenantContext) + check.Assert(err, IsNil) + checkEmpty() + + // Set control access to three users + threeUserSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + Name: users[0].user.User.Name, + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[1].user.User.Href, + //Name: users[1].user.User.Name,// Pass info without name for one of the subjects + Type: users[1].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessFullControl, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[2].user.User.Href, + Name: users[2].user.User.Name, + Type: users[2].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadWrite, + }, + }, + }, + } + err = testAccessControl(catalogName+" catalog three users", catalog, threeUserSettings, threeUserSettings, true, catalogTenantContext, check) + check.Assert(err, IsNil) + + if vcd.client.Client.IsSysAdmin && newOrg != nil { + + // Set control access to three users and an org + threeUserOrgSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + Name: users[0].user.User.Name, + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[1].user.User.Href, + //Name: users[1].user.User.Name,// Pass info without name for one of the subjects + Type: users[1].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessFullControl, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[2].user.User.Href, + Name: users[2].user.User.Name, + Type: users[2].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadWrite, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: newOrg.AdminOrg.HREF, + Name: newOrg.AdminOrg.Name, + Type: newOrg.AdminOrg.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + }, + }, + } + err = testAccessControl(catalogName+" catalog three users and org", catalog, threeUserOrgSettings, threeUserOrgSettings, true, catalogTenantContext, check) + check.Assert(err, IsNil) + + err = catalog.RemoveAccessControl(catalogTenantContext) + check.Assert(err, IsNil) + checkEmpty() + + // Set control access to two org + twoOrgsSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: adminOrg.AdminOrg.HREF, + Name: adminOrg.AdminOrg.Name, + Type: adminOrg.AdminOrg.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessFullControl, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: newOrg.AdminOrg.HREF, + Name: newOrg.AdminOrg.Name, + Type: newOrg.AdminOrg.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + }, + }, + } + err = testAccessControl(catalogName+" catalog two org", catalog, twoOrgsSettings, twoOrgsSettings, true, catalogTenantContext, check) + check.Assert(err, IsNil) + } + + // Set empty settings explicitly + emptySettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + } + err = testAccessControl(catalogName+" catalog empty", catalog, emptySettings, emptySettings, false, catalogTenantContext, check) + check.Assert(err, IsNil) + + checkEmpty() + +} diff --git a/govcd/access_control_test.go b/govcd/access_control_test.go new file mode 100644 index 000000000..1e89bc40e --- /dev/null +++ b/govcd/access_control_test.go @@ -0,0 +1,108 @@ +// +build functional vapp catalog ALL + +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "encoding/json" + "fmt" + + "github.com/kr/pretty" + . "gopkg.in/check.v1" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// accessControlType is an interface used to test access control for all entities that support it +type accessControlType interface { + GetAccessControl(useTenantContext bool) (*types.ControlAccessParams, error) + SetAccessControl(params *types.ControlAccessParams, useTenantContext bool) error + RemoveAccessControl(useTenantContext bool) error + IsShared(useTenantContext bool) (bool, error) + GetId() string +} + +type accessSettingMap map[string]*types.AccessSetting + +func accessSettingsToMap(params types.ControlAccessParams) accessSettingMap { + if params.AccessSettings == nil { + return nil + } + var result = make(accessSettingMap) + + for _, setting := range params.AccessSettings.AccessSetting { + + result[setting.Subject.Name] = setting + } + + return result +} + +// testAccessControl runs an access control test on a target type, identified as an interface. +// * label is a test identifier +// * accessible is the entity being tested (such as a vApp or a catalog) +// * params are the access control parameters to be set +// * expected are the result parameters that we should find in the end result +// * wantShared is whether the final settings should result in the entity being shared +func testAccessControl(label string, accessible accessControlType, params types.ControlAccessParams, expected types.ControlAccessParams, wantShared bool, useTenantContext bool, check *C) error { + + if testVerbose { + fmt.Printf("-- %s\n", label) + } + err := accessible.SetAccessControl(¶ms, useTenantContext) + if err != nil { + return err + } + + foundParams, err := accessible.GetAccessControl(useTenantContext) + if err != nil { + return err + } + + if testVerbose { + text, err := json.MarshalIndent(foundParams, " ", " ") + if err == nil { + fmt.Printf("%s %s\n", label, text) + } + } + if foundParams.IsSharedToEveryone != expected.IsSharedToEveryone { + fmt.Printf("label %s\n", label) + fmt.Printf("found %# v\n", pretty.Formatter(foundParams)) + fmt.Printf("expected %# v\n", pretty.Formatter(expected)) + } + check.Assert(foundParams.IsSharedToEveryone, Equals, expected.IsSharedToEveryone) + if expected.EveryoneAccessLevel != nil { + check.Assert(foundParams.EveryoneAccessLevel, NotNil) + check.Assert(*foundParams.EveryoneAccessLevel, Equals, *expected.EveryoneAccessLevel) + } + if expected.AccessSettings != nil { + expectedMap := accessSettingsToMap(expected) + foundMap := accessSettingsToMap(expected) + + check.Assert(foundParams.AccessSettings, NotNil) + check.Assert(len(foundParams.AccessSettings.AccessSetting), Equals, len(expected.AccessSettings.AccessSetting)) + + for k, v := range expectedMap { + found, exists := foundMap[k] + check.Assert(exists, Equals, true) + if v.Subject.Name != "" { + check.Assert(v.Subject.Name, Equals, found.Subject.Name) + } + check.Assert(v.Subject.Type, Not(Equals), "") + check.Assert(v.Subject.HREF, Not(Equals), "") + check.Assert(v.Subject.HREF, Equals, found.Subject.HREF) + check.Assert(v.AccessLevel, Equals, found.AccessLevel) + } + } + + shared, err := accessible.IsShared(useTenantContext) + if err != nil { + return err + } + check.Assert(shared, Equals, wantShared) + + return nil +} diff --git a/govcd/access_control_vapp_test.go b/govcd/access_control_vapp_test.go new file mode 100644 index 000000000..b3866227b --- /dev/null +++ b/govcd/access_control_vapp_test.go @@ -0,0 +1,234 @@ +// +build functional vapp ALL + +/* + * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "fmt" + "os" + + . "gopkg.in/check.v1" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// vappTenantContext defines whether we use tenant context during vApp tests. +// By default is ON. It is disabled if VCD_VAPP_SYSTEM_CONTEXT is set +var vappTenantContext = os.Getenv("VCD_VAPP_SYSTEM_CONTEXT") == "" + +// GetId completes the implementation of interface accessControlType +func (vapp VApp) GetId() string { + return vapp.VApp.ID +} + +func (vcd *TestVCD) Test_VappAccessControl(check *C) { + + if vcd.config.VCD.Org == "" { + check.Skip("Test_VappAccessControl: Org name not given.") + return + } + if vcd.config.VCD.Vdc == "" { + check.Skip("Test_VappAccessControl: VDC name not given.") + return + } + org, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + check.Assert(org, NotNil) + + vdc, err := org.GetVDCByName(vcd.config.VCD.Vdc, false) + check.Assert(err, IsNil) + check.Assert(vdc, NotNil) + + vappName := "ac-vapp" + var users = []struct { + name string + role string + user *OrgUser + }{ + {"ac-user1", OrgUserRoleVappAuthor, nil}, + {"ac-user2", OrgUserRoleOrganizationAdministrator, nil}, + {"ac-user3", OrgUserRoleCatalogAuthor, nil}, + } + + // Create a new vApp + vapp, err := makeEmptyVapp(vdc, vappName) + check.Assert(err, IsNil) + check.Assert(vapp, NotNil) + AddToCleanupList(vappName, "vapp", vcd.config.VCD.Org+"|"+vcd.config.VCD.Vdc, "Test_VappAccessControl") + + checkEmpty := func() { + settings, err := vapp.GetAccessControl(vappTenantContext) + check.Assert(err, IsNil) + check.Assert(settings.IsSharedToEveryone, Equals, false) // There should not be a global sharing + check.Assert(settings.AccessSettings, IsNil) // There should not be any explicit sharing + } + + // Create three users + for i := 0; i < len(users); i++ { + users[i].user, err = org.CreateUserSimple(OrgUserConfiguration{ + Name: users[i].name, Password: users[i].name, RoleName: users[i].role, IsEnabled: true, + }) + check.Assert(err, IsNil) + check.Assert(users[i].user, NotNil) + AddToCleanupList(users[i].name, "user", vcd.config.VCD.Org, "Test_VappAccessControl") + } + + // Clean up environment + defer func() { + if testVerbose { + fmt.Printf("deleting %s\n", vappName) + } + task, err := vapp.Delete() + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + for i := 0; i < len(users); i++ { + if testVerbose { + fmt.Printf("deleting %s\n", users[i].name) + } + err = users[i].user.Delete(false) + check.Assert(err, IsNil) + } + }() + checkEmpty() + + // Set access control to every user and group + allUsersSettings := types.ControlAccessParams{ + EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadOnly), + IsSharedToEveryone: true, + } + + // Use generic testAccessControl. Here vapp is passed as accessControlType interface + err = testAccessControl("vapp all users RO", vapp, allUsersSettings, allUsersSettings, true, vappTenantContext, check) + check.Assert(err, IsNil) + + allUsersSettings = types.ControlAccessParams{ + EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadWrite), + IsSharedToEveryone: true, + } + err = testAccessControl("vapp all users R/W", vapp, allUsersSettings, allUsersSettings, true, vappTenantContext, check) + check.Assert(err, IsNil) + + // Set access control to one user + oneUserSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + Name: users[0].user.User.Name, + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadWrite, + }, + }, + }, + } + err = testAccessControl("vapp one user", vapp, oneUserSettings, oneUserSettings, true, vappTenantContext, check) + check.Assert(err, IsNil) + + // Check that vapp.GetAccessControl and vdc.GetVappAccessControl return the same data + controlAccess, err := vapp.GetAccessControl(vappTenantContext) + check.Assert(err, IsNil) + vdcControlAccessName, err := vdc.GetVappAccessControl(vappName, vappTenantContext) + check.Assert(err, IsNil) + check.Assert(controlAccess, DeepEquals, vdcControlAccessName) + + vdcControlAccessId, err := vdc.GetVappAccessControl(vapp.VApp.ID, vappTenantContext) + check.Assert(err, IsNil) + check.Assert(controlAccess, DeepEquals, vdcControlAccessId) + + // Set access control to two users + twoUserSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + //Name: users[0].user.User.Name, // Pass info without name for one of the subjects + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[1].user.User.Href, + Name: users[1].user.User.Name, + Type: users[1].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessFullControl, + }, + }, + }, + } + err = testAccessControl("vapp two users", vapp, twoUserSettings, twoUserSettings, true, vappTenantContext, check) + check.Assert(err, IsNil) + + // Check removal of sharing setting + err = vapp.RemoveAccessControl(vappTenantContext) + check.Assert(err, IsNil) + checkEmpty() + + // Set access control to three users + threeUserSettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: nil, + AccessSettings: &types.AccessSettingList{ + []*types.AccessSetting{ + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[0].user.User.Href, + Name: users[0].user.User.Name, + Type: users[0].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadOnly, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[1].user.User.Href, + //Name: users[1].user.User.Name,// Pass info without name for one of the subjects + Type: users[1].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessFullControl, + }, + &types.AccessSetting{ + Subject: &types.LocalSubject{ + HREF: users[2].user.User.Href, + Name: users[2].user.User.Name, + Type: users[2].user.User.Type, + }, + ExternalSubject: nil, + AccessLevel: types.ControlAccessReadWrite, + }, + }, + }, + } + err = testAccessControl("vapp three users", vapp, threeUserSettings, threeUserSettings, true, vappTenantContext, check) + check.Assert(err, IsNil) + + // Set empty settings explicitly + emptySettings := types.ControlAccessParams{ + IsSharedToEveryone: false, + } + err = testAccessControl("vapp empty", vapp, emptySettings, emptySettings, false, vappTenantContext, check) + check.Assert(err, IsNil) + + checkEmpty() + + orgInfo, err := vapp.getOrgInfo() + check.Assert(err, IsNil) + check.Assert(orgInfo.id, Equals, extractUuid(org.AdminOrg.ID)) + check.Assert(orgInfo.name, Equals, org.AdminOrg.Name) +} diff --git a/govcd/admincatalog.go b/govcd/admincatalog.go index 8a63835fe..cd95b2f02 100644 --- a/govcd/admincatalog.go +++ b/govcd/admincatalog.go @@ -84,3 +84,8 @@ func (adminCatalog *AdminCatalog) Refresh() error { return nil } + +// getOrgInfo finds the organization to which the admin catalog belongs, and returns its name and ID +func (adminCatalog *AdminCatalog) getOrgInfo() (orgInfoType, error) { + return getOrgInfo(adminCatalog.client, adminCatalog.AdminCatalog.Link, adminCatalog.AdminCatalog.ID, adminCatalog.AdminCatalog.Name, "AdminCatalog") +} diff --git a/govcd/adminvdc.go b/govcd/adminvdc.go index 43a8478ec..c3e0c6581 100644 --- a/govcd/adminvdc.go +++ b/govcd/adminvdc.go @@ -387,3 +387,16 @@ func validateVdcConfigurationV97(vdcDefinition types.VdcConfiguration) error { } return nil } + +// GetVappList returns the list of vApps for an Admin VDC +func (vdc *AdminVdc) GetVappList() []*types.ResourceReference { + var list []*types.ResourceReference + for _, resourceEntities := range vdc.AdminVdc.ResourceEntities { + for _, resourceReference := range resourceEntities.ResourceEntity { + if resourceReference.Type == types.MimeVApp { + list = append(list, resourceReference) + } + } + } + return list +} diff --git a/govcd/api.go b/govcd/api.go index b06afd795..74d087930 100644 --- a/govcd/api.go +++ b/govcd/api.go @@ -203,7 +203,7 @@ func (cli *Client) newRequest(params map[string]string, notEncodedParams map[str } // Merge in additional headers before logging if any where specified in additionalHeader - // paramter + // parameter if additionalHeader != nil && len(additionalHeader) > 0 { for headerName, headerValueSlice := range additionalHeader { for _, singleHeaderValue := range headerValueSlice { diff --git a/govcd/api_vcd_test.go b/govcd/api_vcd_test.go index fa29905b7..6e0cbab35 100644 --- a/govcd/api_vcd_test.go +++ b/govcd/api_vcd_test.go @@ -1317,7 +1317,7 @@ func (vcd *TestVCD) TearDownSuite(check *C) { // CleanupEntityList. Entities that have already been cleaned up by their // functions will be ignored. for i, cleanupEntity := range cleanupEntityList { - fmt.Printf("# %d ", i+1) + fmt.Printf("# %d of %d - ", i+1, len(cleanupEntityList)) vcd.removeLeftoverEntities(cleanupEntity) removePersistentCleanupList() } diff --git a/govcd/catalog.go b/govcd/catalog.go index b0f1691e5..427dbcc76 100644 --- a/govcd/catalog.go +++ b/govcd/catalog.go @@ -810,3 +810,44 @@ func (catalog *Catalog) QueryMediaList() ([]*types.MediaRecordType, error) { } return mediaResults, nil } + +// getOrgInfo finds the organization to which the entity belongs, and returns its name and ID +func getOrgInfo(client *Client, links types.LinkList, id, name, entityType string) (orgInfoType, error) { + previous, exists := orgInfoCache[id] + if exists { + return previous, nil + } + var orgId string + var orgHref string + var err error + for _, link := range links { + if link.Rel == "up" && (link.Type == types.MimeOrg || link.Type == types.MimeAdminOrg) { + orgId, err = GetUuidFromHref(link.HREF, true) + if err != nil { + return orgInfoType{}, err + } + orgHref = link.HREF + break + } + } + if orgHref == "" || orgId == "" { + return orgInfoType{}, fmt.Errorf("error retrieving org info for %s %s", entityType, name) + } + var org types.Org + _, err = client.ExecuteRequest(orgHref, http.MethodGet, + "", "error retrieving org: %s", nil, &org) + if err != nil { + return orgInfoType{}, err + } + + orgInfoCache[id] = orgInfoType{ + id: orgId, + name: org.Name, + } + return orgInfoType{name: org.Name, id: orgId}, nil +} + +// getOrgInfo finds the organization to which the catalog belongs, and returns its name and ID +func (catalog *Catalog) getOrgInfo() (orgInfoType, error) { + return getOrgInfo(catalog.client, catalog.Catalog.Link, catalog.Catalog.ID, catalog.Catalog.Name, "Catalog") +} diff --git a/govcd/common_test.go b/govcd/common_test.go index 2b5fe332a..a28727800 100644 --- a/govcd/common_test.go +++ b/govcd/common_test.go @@ -1,4 +1,4 @@ -// +build api functional catalog vapp gateway network org query extnetwork task vm vdc system disk lb lbAppRule lbAppProfile lbServerPool lbServiceMonitor lbVirtualServer user nsxv ALL +// +build api functional catalog vapp gateway network org query extnetwork task vm vdc system disk lb lbAppRule lbAppProfile lbServerPool lbServiceMonitor lbVirtualServer user nsxv affinity ALL /* * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. @@ -668,3 +668,27 @@ func deleteVapp(vcd *TestVCD, name string) error { } return nil } + +// makeEmptyVapp creates a given vApp without any VM +func makeEmptyVapp(vdc *Vdc, name string) (*VApp, error) { + + err := vdc.ComposeRawVApp(name) + if err != nil { + return nil, err + } + vapp, err := vdc.GetVAppByName(name, true) + if err != nil { + return nil, err + } + initialVappStatus, err := vapp.GetStatus() + if err != nil { + return nil, err + } + if initialVappStatus != "RESOLVED" { + err = vapp.BlockWhileStatus(initialVappStatus, vapp.client.MaxRetryTimeout) + if err != nil { + return nil, err + } + } + return vapp, nil +} diff --git a/govcd/filter_engine_test.go b/govcd/filter_engine_test.go index 97af1b31c..7e051314e 100644 --- a/govcd/filter_engine_test.go +++ b/govcd/filter_engine_test.go @@ -45,11 +45,7 @@ func (vcd *TestVCD) Test_SearchSpecificVappTemplate(check *C) { } } - queryType := types.QtVappTemplate - - if client.IsSysAdmin { - queryType = types.QtAdminVappTemplate - } + queryType := client.GetQueryType(types.QtVappTemplate) // metadata filters var tests = []struct { @@ -114,10 +110,7 @@ func (vcd *TestVCD) Test_SearchVappTemplate(check *C) { check.Assert(err, IsNil) client := catalog.client - queryType := types.QtVappTemplate - if client.IsSysAdmin { - queryType = types.QtAdminVappTemplate - } + queryType := client.GetQueryType(types.QtVappTemplate) // Test with any vApp templates, using mass produced filters filters, err := HelperMakeFiltersFromVappTemplate(catalog) check.Assert(err, IsNil) @@ -160,10 +153,7 @@ func (vcd *TestVCD) Test_SearchCatalogItem(check *C) { check.Assert(err, IsNil) client := catalog.client - queryType := types.QtCatalogItem - if client.IsSysAdmin { - queryType = types.QtAdminCatalogItem - } + queryType := client.GetQueryType(types.QtCatalogItem) // Test with any catalog items, using mass produced filters filters, err := HelperMakeFiltersFromCatalogItem(catalog) check.Assert(err, IsNil) @@ -271,10 +261,7 @@ func (vcd *TestVCD) Test_SearchCatalog(check *C) { check.Assert(err, IsNil) check.Assert(filters, NotNil) - queryType := types.QtCatalog - if client.Client.IsSysAdmin { - queryType = types.QtAdminCatalog - } + queryType := client.Client.GetQueryType(types.QtCatalog) for _, fm := range filters { queryItems, explanation, err := org.SearchByFilter(queryType, fm.Criteria) check.Assert(err, IsNil) @@ -309,11 +296,8 @@ func (vcd *TestVCD) Test_SearchMediaItem(check *C) { check.Assert(err, IsNil) check.Assert(filters, NotNil) - queryType := types.QtMedia + queryType := client.Client.GetQueryType(types.QtMedia) - if client.Client.IsSysAdmin { - queryType = types.QtAdminMedia - } for _, fm := range filters { queryItems, explanation, err := catalog.SearchByFilter(queryType, "catalog", fm.Criteria) check.Assert(err, IsNil) diff --git a/govcd/filter_interface.go b/govcd/filter_interface.go index 6cc1b217c..d49b566f9 100644 --- a/govcd/filter_interface.go +++ b/govcd/filter_interface.go @@ -32,6 +32,8 @@ type ( QueryCatalog types.CatalogRecord QueryOrgVdcNetwork types.QueryResultOrgVdcNetworkRecordType QueryMedia types.MediaRecordType + QueryVapp types.QueryResultVAppRecordType + QueryVm types.QueryResultVMRecordType ) // getMetadataValue is a generic metadata lookup for all query items @@ -155,6 +157,34 @@ func (network QueryOrgVdcNetwork) GetMetadataValue(key string) string { return getMetadataValue(network.Metadata, key) } +// -------------------------------------------------------------- +// vApp +// -------------------------------------------------------------- +func (vapp QueryVapp) GetHref() string { return vapp.HREF } +func (vapp QueryVapp) GetName() string { return vapp.Name } +func (vapp QueryVapp) GetType() string { return "vApp" } +func (vapp QueryVapp) GetIp() string { return "" } +func (vapp QueryVapp) GetDate() string { return vapp.CreationDate } +func (vapp QueryVapp) GetParentName() string { return vapp.VdcName } +func (vapp QueryVapp) GetParentId() string { return vapp.VdcHREF } +func (vapp QueryVapp) GetMetadataValue(key string) string { + return getMetadataValue(vapp.MetaData, key) +} + +// -------------------------------------------------------------- +// VM +// -------------------------------------------------------------- +func (vm QueryVm) GetHref() string { return vm.HREF } +func (vm QueryVm) GetName() string { return vm.Name } +func (vm QueryVm) GetType() string { return "VM" } +func (vm QueryVm) GetIp() string { return vm.IpAddress } +func (vm QueryVm) GetDate() string { return vm.DateCreated } +func (vm QueryVm) GetParentName() string { return vm.ContainerName } +func (vm QueryVm) GetParentId() string { return vm.VdcHREF } +func (vm QueryVm) GetMetadataValue(key string) string { + return getMetadataValue(vm.MetaData, key) +} + // -------------------------------------------------------------- // result conversion // -------------------------------------------------------------- @@ -206,9 +236,39 @@ func resultToQueryItems(queryType string, results Results) ([]QueryItem, error) for i, item := range results.Results.AdminCatalogRecord { items[i] = QueryAdminCatalog(*item) } + case types.QtVm: + for i, item := range results.Results.VMRecord { + items[i] = QueryVm(*item) + } + case types.QtAdminVm: + for i, item := range results.Results.AdminVMRecord { + items[i] = QueryVm(*item) + } + case types.QtVapp: + for i, item := range results.Results.VAppRecord { + items[i] = QueryVapp(*item) + } + case types.QtAdminVapp: + for i, item := range results.Results.AdminVAppRecord { + items[i] = QueryVapp(*item) + } } if len(items) > 0 { return items, nil } return nil, fmt.Errorf("unsupported query type %s", queryType) } + +// GetQueryType is an utility function to get the appropriate query type depending on +// the user's role +func (client Client) GetQueryType(queryType string) string { + if client.IsSysAdmin { + adminType, ok := types.AdminQueryTypes[queryType] + if ok { + return adminType + } else { + panic(fmt.Sprintf("no corresponding admin type found for type %s", queryType)) + } + } + return queryType +} diff --git a/govcd/group.go b/govcd/group.go index 409953a3f..263dd418f 100644 --- a/govcd/group.go +++ b/govcd/group.go @@ -98,7 +98,7 @@ func (adminOrg *AdminOrg) GetGroupById(id string, refresh bool) (*OrgGroup, erro } for _, group := range adminOrg.AdminOrg.Groups.Group { - if group.ID == id { + if equalIds(id, group.ID, group.HREF) { return adminOrg.GetGroupByHref(group.HREF) } } diff --git a/govcd/org.go b/govcd/org.go index 73fc8f00f..c66122e6f 100644 --- a/govcd/org.go +++ b/govcd/org.go @@ -309,10 +309,7 @@ func (org *Org) GetVDCByNameOrId(identifier string, refresh bool) (*Vdc, error) // QueryCatalogList returns a list of catalogs for this organization func (org *Org) QueryCatalogList() ([]*types.CatalogRecord, error) { util.Logger.Printf("[DEBUG] QueryCatalogList with org name %s", org.Org.Name) - queryType := types.QtCatalog - if org.client.IsSysAdmin { - queryType = types.QtAdminCatalog - } + queryType := org.client.GetQueryType(types.QtCatalog) results, err := org.client.cumulativeQuery(queryType, nil, map[string]string{ "type": queryType, "filter": fmt.Sprintf("orgName==%s", url.QueryEscape(org.Org.Name)), diff --git a/govcd/query_metadata.go b/govcd/query_metadata.go index 1c808d43d..d38064636 100644 --- a/govcd/query_metadata.go +++ b/govcd/query_metadata.go @@ -63,7 +63,12 @@ func queryFieldsOnDemand(queryType string) ([]string, error) { "gcStatus", "guestOs", "hardwareVersion", "hostName", "isAutoNature", "isDeleted", "isDeployed", "isPublished", "isVAppTemplate", "isVdcEnabled", "memoryMB", "moref", "name", "numberOfCpus", "org", "status", "storageProfileName", "vc", "vdc", "vmToolsVersion", "containerStatus", "pvdcHighestSupportedHardwareVersion", - } + "isComputePolicyCompliant", "vmSizingPolicyId", "vmPlacementPolicyId", "encrypted", "dateCreated", + "totalStorageAllocatedMb", "isExpired"} + vappFields = []string{"creationDate", "isBusy", "isDeployed", "isEnabled", "isExpired", "isInMaintenanceMode", "isPublic", + "ownerName", "status", "vdc", "vdcName", "numberOfVMs", "numberOfCpus", "cpuAllocationMhz", "cpuAllocationInMhz", + "storageKB", "memoryAllocationMB", "isAutoDeleteNotified", "isAutoUndeployNotified", "isVdcEnabled", "honorBookOrder", + "pvdcHighestSupportedHardwareVersion", "lowestHardwareVersionInVApp"} fieldsOnDemand = map[string][]string{ types.QtVappTemplate: vappTemplatefields, types.QtAdminVappTemplate: vappTemplatefields, @@ -77,6 +82,8 @@ func queryFieldsOnDemand(queryType string) ([]string, error) { types.QtAdminCatalogItem: catalogItemFields, types.QtVm: vmFields, types.QtAdminVm: vmFields, + types.QtVapp: vappFields, + types.QtAdminVapp: vappFields, } ) @@ -128,6 +135,12 @@ func addResults(queryType string, cumulativeResults, newResults Results) (Result case types.QtAdminVm: cumulativeResults.Results.AdminVMRecord = append(cumulativeResults.Results.AdminVMRecord, newResults.Results.AdminVMRecord...) size = len(newResults.Results.AdminVMRecord) + case types.QtVapp: + cumulativeResults.Results.VAppRecord = append(cumulativeResults.Results.VAppRecord, newResults.Results.VAppRecord...) + size = len(newResults.Results.VAppRecord) + case types.QtAdminVapp: + cumulativeResults.Results.AdminVAppRecord = append(cumulativeResults.Results.AdminVAppRecord, newResults.Results.AdminVAppRecord...) + size = len(newResults.Results.AdminVAppRecord) default: return Results{}, 0, fmt.Errorf("query type %s not supported", queryType) @@ -151,6 +164,8 @@ func (client *Client) cumulativeQuery(queryType string, params, notEncodedParams types.QtAdminCatalogItem, types.QtVm, types.QtAdminVm, + types.QtVapp, + types.QtAdminVapp, } // Make sure the query type is supported // We need to check early, as queries that would return less than 25 items (default page size) would succeed, diff --git a/govcd/user.go b/govcd/user.go index 8510888a6..d2cc2a502 100644 --- a/govcd/user.go +++ b/govcd/user.go @@ -146,7 +146,7 @@ func (adminOrg *AdminOrg) GetUserById(id string, refresh bool) (*OrgUser, error) } for _, user := range adminOrg.AdminOrg.Users.User { - if user.ID == id { + if equalIds(id, user.ID, user.HREF) { return adminOrg.GetUserByHref(user.HREF) } } diff --git a/govcd/vapp.go b/govcd/vapp.go index 15a275a34..9a33770ff 100644 --- a/govcd/vapp.go +++ b/govcd/vapp.go @@ -66,7 +66,7 @@ func (vapp *VApp) getParentVDC() (Vdc, error) { vdc := NewVdc(vapp.client) _, err := vapp.client.ExecuteRequest(link.HREF, http.MethodGet, - "", "error retrieving paren vdc: %s", nil, vdc.Vdc) + "", "error retrieving parent vdc: %s", nil, vdc.Vdc) if err != nil { return Vdc{}, err } @@ -1332,3 +1332,37 @@ func (vapp *VApp) GetVMByNameOrId(identifier string, refresh bool) (*VM, error) } return entity.(*VM), err } + +// QueryVappList returns a list of all vApps in all the organizations available to the caller +func (client *Client) QueryVappList() ([]*types.QueryResultVAppRecordType, error) { + var vappList []*types.QueryResultVAppRecordType + queryType := client.GetQueryType(types.QtVapp) + params := map[string]string{ + "type": queryType, + "filterEncoded": "true", + } + vappResult, err := client.cumulativeQuery(queryType, nil, params) + if err != nil { + return nil, fmt.Errorf("error getting vApp list : %s", err) + } + vappList = vappResult.Results.VAppRecord + if client.IsSysAdmin { + vappList = vappResult.Results.AdminVAppRecord + } + return vappList, nil +} + +// getOrgInfo finds the organization to which the vApp belongs (through the VDC), and returns its name and ID +func (vapp *VApp) getOrgInfo() (orgInfoType, error) { + previous, exists := orgInfoCache[vapp.VApp.ID] + if exists { + return previous, nil + } + //var orgHref string + var err error + vdc, err := vapp.getParentVDC() + if err != nil { + return orgInfoType{}, err + } + return getOrgInfo(vapp.client, vdc.Vdc.Link, vapp.VApp.ID, vapp.VApp.Name, "vApp") +} diff --git a/govcd/vdc.go b/govcd/vdc.go index 01d62c209..33c2c7356 100644 --- a/govcd/vdc.go +++ b/govcd/vdc.go @@ -887,3 +887,16 @@ func (vdc *Vdc) getLinkHref(rel, linkType string) string { } return "" } + +// GetVappList returns the list of vApps for a VDC +func (vdc *Vdc) GetVappList() []*types.ResourceReference { + var list []*types.ResourceReference + for _, resourceEntities := range vdc.Vdc.ResourceEntities { + for _, resourceReference := range resourceEntities.ResourceEntity { + if resourceReference.Type == types.MimeVApp { + list = append(list, resourceReference) + } + } + } + return list +} diff --git a/govcd/vdc_test.go b/govcd/vdc_test.go index 9f768507a..3aeda28bf 100644 --- a/govcd/vdc_test.go +++ b/govcd/vdc_test.go @@ -346,3 +346,103 @@ func (vcd *TestVCD) Test_GetVApp(check *C) { } vcd.testFinderGetGenericEntity(def, check) } + +// TestGetVappList tests all methods that retrieve a list of vApps +// vdc.GetVappList +// adminVdc.GetVappList +// client.QueryVappList +// client.SearchByFilter +func (vcd *TestVCD) TestGetVappList(check *C) { + + if vcd.skipVappTests { + check.Skip("Skipping test because vapp wasn't properly created") + } + if vcd.config.VCD.Org == "" { + check.Skip("Test_GetVapp: Org name not given") + return + } + if vcd.config.VCD.Vdc == "" { + check.Skip("Test_GetVapp: VDC name not given") + return + } + vapp := vcd.findFirstVapp() + if vapp == (VApp{}) { + check.Skip("no vApp found") + return + } + + org, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + check.Assert(org, NotNil) + + vdc, err := org.GetVDCByName(vcd.config.VCD.Vdc, false) + check.Assert(err, IsNil) + check.Assert(vdc, NotNil) + adminVdc, err := org.GetAdminVDCByName(vcd.config.VCD.Vdc, false) + check.Assert(err, IsNil) + check.Assert(adminVdc, NotNil) + + // Get the vApp list from VDC + vappList := vdc.GetVappList() + check.Assert(vappList, NotNil) + check.Assert(len(vappList), Not(Equals), 0) + + // Get the vApp list from admin VDC + vappAdminList := adminVdc.GetVappList() + check.Assert(vappAdminList, NotNil) + check.Assert(len(vappAdminList), Not(Equals), 0) + check.Assert(len(vappAdminList), Equals, len(vappList)) + + // Check that the known vApp is found in both lists + foundVappInList := false + foundVappInAdminList := false + foundVappInQueryList := false + for _, ref := range vappList { + if ref.ID == vapp.VApp.ID { + foundVappInList = true + } + } + for _, ref := range vappAdminList { + if ref.ID == vapp.VApp.ID { + foundVappInAdminList = true + } + } + check.Assert(foundVappInList, Equals, true) + check.Assert(foundVappInAdminList, Equals, true) + + // Get the vApp list with a query (returns all vApps visible to user, non only the ones withing the current VDC) + queryVappList, err := vcd.client.Client.QueryVappList() + check.Assert(err, IsNil) + check.Assert(queryVappList, NotNil) + + for _, qItem := range queryVappList { + if qItem.HREF == vapp.VApp.HREF { + foundVappInQueryList = true + } + } + check.Assert(foundVappInQueryList, Equals, true) + + // Use the search engine to find the known vApp + criteria := NewFilterDef() + criteria.AddFilter(types.FilterNameRegex, TestSetUpSuite) + queryType := vcd.client.Client.GetQueryType(types.QtVapp) + queryItems, _, err := vcd.client.Client.SearchByFilter(queryType, criteria) + check.Assert(err, IsNil) + check.Assert(queryItems, NotNil) + check.Assert(len(queryItems), Not(Equals), 0) + check.Assert(queryItems[0].GetHref(), Equals, vapp.VApp.HREF) + + // Use the search engine to also find the known VM + vm, vmName := vcd.findFirstVm(vapp) + check.Assert(vmName, Not(Equals), "") + check.Assert(vm.HREF, Not(Equals), "") + criteria = NewFilterDef() + criteria.AddFilter(types.FilterNameRegex, vmName) + criteria.AddFilter(types.FilterParent, vapp.VApp.Name) + queryType = vcd.client.Client.GetQueryType(types.QtVm) + queryItems, _, err = vcd.client.Client.SearchByFilter(queryType, criteria) + check.Assert(err, IsNil) + check.Assert(queryItems, NotNil) + check.Assert(len(queryItems), Not(Equals), 0) + check.Assert(vm.HREF, Equals, queryItems[0].GetHref()) +} diff --git a/govcd/vm.go b/govcd/vm.go index 52a0a4fe1..35bb342b5 100644 --- a/govcd/vm.go +++ b/govcd/vm.go @@ -1477,10 +1477,7 @@ func (vm *VM) UpdateVmSpecSectionAsync(vmSettingsToUpdate *types.VmSpecSection, // QueryVmList returns a list of all VMs in all the organizations available to the caller func (client *Client) QueryVmList(filter types.VmQueryFilter) ([]*types.QueryResultVMRecordType, error) { var vmList []*types.QueryResultVMRecordType - queryType := types.QtVm - if client.IsSysAdmin { - queryType = types.QtAdminVm - } + queryType := client.GetQueryType(types.QtVm) params := map[string]string{ "type": queryType, "filterEncoded": "true", diff --git a/govcd/vm_affinity_rule_test.go b/govcd/vm_affinity_rule_test.go index 49dc8761e..9cbbf0a18 100644 --- a/govcd/vm_affinity_rule_test.go +++ b/govcd/vm_affinity_rule_test.go @@ -272,30 +272,6 @@ func (vcd *TestVCD) Test_VmAffinityRule(check *C) { } -// makeEmptyVapp creates a given vApp without any VM -func makeEmptyVapp(vdc *Vdc, name string) (*VApp, error) { - - err := vdc.ComposeRawVApp(name) - if err != nil { - return nil, err - } - vapp, err := vdc.GetVAppByName(name, true) - if err != nil { - return nil, err - } - initialVappStatus, err := vapp.GetStatus() - if err != nil { - return nil, err - } - if initialVappStatus != "RESOLVED" { - err = vapp.BlockWhileStatus(initialVappStatus, vapp.client.MaxRetryTimeout) - if err != nil { - return nil, err - } - } - return vapp, nil -} - // makeEmptyVm creates an empty VM inside a given vApp func makeEmptyVm(vapp *VApp, name string) (*VM, error) { newDisk := types.DiskSettings{ diff --git a/types/v56/constants.go b/types/v56/constants.go index be46e9db5..dd83c3dce 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -31,6 +31,8 @@ const ( MimeOrgList = "application/vnd.vmware.vcloud.orgList+xml" // MimeOrg mime for org MimeOrg = "application/vnd.vmware.vcloud.org+xml" + // MimeAdminOrg mime for admin org + MimeAdminOrg = "application/vnd.vmware.admin.organization+xml" // MimeCatalog mime for catalog MimeCatalog = "application/vnd.vmware.vcloud.catalog+xml" // MimeCatalogItem mime for catalog item @@ -117,6 +119,8 @@ const ( MimeOrgLdapSettings = "application/vnd.vmware.admin.organizationldapsettings+xml" // Mime of vApp network MimeVappNetwork = "application/vnd.vmware.vcloud.vAppNetwork+xml" + // Mime of access control + MimeControlAccess = "application/vnd.vmware.vcloud.controlAccess+xml" // Mime of VM capabilities MimeVmCapabilities = "application/vnd.vmware.vcloud.vmCapabilitiesSection+xml" ) @@ -236,8 +240,22 @@ const ( QtMedia = "media" // media item QtVm = "vm" // Virtual machine QtAdminVm = "adminVM" // Virtual machine as admin + QtVapp = "vApp" // vApp + QtAdminVapp = "adminVApp" // vApp as admin ) +// AdminQueryTypes returns the corresponding "admin" query type for each regular type +var AdminQueryTypes = map[string]string{ + QtEdgeGateway: QtEdgeGateway, // EdgeGateway query type is the same for admin and regular users + QtOrgVdcNetwork: QtOrgVdcNetwork, // Org VDC Network query type is the same for admin and regular users + QtVappTemplate: QtAdminVappTemplate, + QtCatalog: QtAdminCatalog, + QtCatalogItem: QtAdminCatalogItem, + QtMedia: QtAdminMedia, + QtVm: QtAdminVm, + QtVapp: QtAdminVapp, +} + const ( // Affinity and anti affinity definitions PolarityAffinity = "Affinity" @@ -278,6 +296,13 @@ const ( LdapModeCustom = "CUSTOM" ) +// Access control modes +const ( + ControlAccessReadOnly = "ReadOnly" + ControlAccessReadWrite = "Change" + ControlAccessFullControl = "FullControl" +) + // BodyType allows to define API body types where applicable type BodyType int @@ -299,3 +324,11 @@ const ( OpenApiPathVersion1_0_0 = "1.0.0/" OpenApiEndpointRoles = "roles/" ) + +// Header keys to run operations in tenant context +const ( + // HeaderTenantContext requires the Org ID of the tenant + HeaderTenantContext = "X-VMWARE-VCLOUD-TENANT-CONTEXT" + // HeaderAuthContext requires the Org name of the tenant + HeaderAuthContext = "X-VMWARE-VCLOUD-AUTH-CONTEXT" +) diff --git a/types/v56/types.go b/types/v56/types.go index 6fc9e428d..71770cbdb 100644 --- a/types/v56/types.go +++ b/types/v56/types.go @@ -2138,6 +2138,7 @@ type QueryResultRecordsType struct { VMRecord []*QueryResultVMRecordType `xml:"VMRecord"` // A record representing a VM result. AdminVMRecord []*QueryResultVMRecordType `xml:"AdminVMRecord"` // A record representing a Admin VM result. VAppRecord []*QueryResultVAppRecordType `xml:"VAppRecord"` // A record representing a VApp result. + AdminVAppRecord []*QueryResultVAppRecordType `xml:"AdminVAppRecord"` // A record representing a VApp result as admin. OrgVdcStorageProfileRecord []*QueryResultOrgVdcStorageProfileRecordType `xml:"OrgVdcStorageProfileRecord"` // A record representing storage profiles MediaRecord []*MediaRecordType `xml:"MediaRecord"` // A record representing media AdminMediaRecord []*MediaRecordType `xml:"AdminMediaRecord"` // A record representing Admin media @@ -2226,74 +2227,82 @@ type QueryResultEdgeGatewayRecordType struct { // QueryResultVMRecordType represents a VM record as query result. type QueryResultVMRecordType struct { // Attributes - HREF string `xml:"href,attr,omitempty"` // The URI of the entity. - ID string `xml:"id,attr,omitempty"` - Name string `xml:"name,attr,omitempty"` // VM name. - Type string `xml:"type,attr,omitempty"` // Contains the type of the resource. - ContainerName string `xml:"containerName,attr,omitempty"` // The name of the vApp or vApp template that contains this VM. - ContainerID string `xml:"container,attr,omitempty"` // The ID of the vApp or vApp template that contains this VM. - OwnerName string `xml:"ownerName,attr,omitempty"` - Owner string `xml:"owner,attr,omitempty"` - VdcHREF string `xml:"vdc,attr,omitempty"` - VAppTemplate bool `xml:"isVAppTemplate,attr,omitempty"` - Deleted bool `xml:"isDeleted,attr,omitempty"` - GuestOS string `xml:"guestOs,attr,omitempty"` - Cpus int `xml:"numberOfCpus,attr,omitempty"` - MemoryMB int `xml:"memoryMB,attr,omitempty"` - Status string `xml:"status,attr,omitempty"` - NetworkName string `xml:"networkName,attr,omitempty"` - NetworkHref string `xml:"network,attr,omitempty"` - IpAddress string `xml:"ipAddress,attr,omitempty"` // If configured, the IP Address of the VM on the primary network, otherwise empty. - Busy bool `xml:"isBusy,attr,omitempty"` - Deployed bool `xml:"isDeployed,attr,omitempty"` // True if the virtual machine is deployed. - Published bool `xml:"isPublished,attr,omitempty"` - CatalogName string `xml:"catalogName,attr,omitempty"` - HardwareVersion int `xml:"hardwareVersion,attr,omitempty"` - VmToolsStatus string `xml:"vmToolsStatus,attr,omitempty"` - MaintenanceMode bool `xml:"isInMaintenanceMode,attr,omitempty"` - AutoNature bool `xml:"isAutoNature,attr,omitempty"` // True if the parent vApp is a managed vApp - StorageProfileName string `xml:"storageProfileName,attr,omitempty"` - GcStatus string `xml:"gcStatus,attr,omitempty"` // GC status of this VM. - AutoUndeployDate string `xml:"autoUndeployDate,attr,omitempty"` - AutoDeleteDate string `xml:"autoDeleteDate,attr,omitempty"` - AutoUndeployNotified bool `xml:"isAutoUndeployNotified,attr,omitempty"` - AutoDeleteNotified bool `xml:"isAutoDeleteNotified,attr,omitempty"` - Link []*Link `xml:"Link,omitempty"` - MetaData *Metadata `xml:"Metadata,omitempty"` + HREF string `xml:"href,attr,omitempty"` // The URI of the entity. + ID string `xml:"id,attr,omitempty"` + Name string `xml:"name,attr,omitempty"` // VM name. + Type string `xml:"type,attr,omitempty"` // Contains the type of the resource. + ContainerName string `xml:"containerName,attr,omitempty"` // The name of the vApp or vApp template that contains this VM. + ContainerID string `xml:"container,attr,omitempty"` // The ID of the vApp or vApp template that contains this VM. + OwnerName string `xml:"ownerName,attr,omitempty"` + Owner string `xml:"owner,attr,omitempty"` + VdcHREF string `xml:"vdc,attr,omitempty"` + VAppTemplate bool `xml:"isVAppTemplate,attr,omitempty"` + Deleted bool `xml:"isDeleted,attr,omitempty"` + GuestOS string `xml:"guestOs,attr,omitempty"` + Cpus int `xml:"numberOfCpus,attr,omitempty"` + MemoryMB int `xml:"memoryMB,attr,omitempty"` + Status string `xml:"status,attr,omitempty"` + NetworkName string `xml:"networkName,attr,omitempty"` + NetworkHref string `xml:"network,attr,omitempty"` + IpAddress string `xml:"ipAddress,attr,omitempty"` // If configured, the IP Address of the VM on the primary network, otherwise empty. + Busy bool `xml:"isBusy,attr,omitempty"` + Deployed bool `xml:"isDeployed,attr,omitempty"` // True if the virtual machine is deployed. + Published bool `xml:"isPublished,attr,omitempty"` + CatalogName string `xml:"catalogName,attr,omitempty"` + HardwareVersion int `xml:"hardwareVersion,attr,omitempty"` + VmToolsStatus string `xml:"vmToolsStatus,attr,omitempty"` + MaintenanceMode bool `xml:"isInMaintenanceMode,attr,omitempty"` + AutoNature bool `xml:"isAutoNature,attr,omitempty"` // True if the parent vApp is a managed vApp + StorageProfileName string `xml:"storageProfileName,attr,omitempty"` + GcStatus string `xml:"gcStatus,attr,omitempty"` // GC status of this VM. + AutoUndeployDate string `xml:"autoUndeployDate,attr,omitempty"` + AutoDeleteDate string `xml:"autoDeleteDate,attr,omitempty"` + AutoUndeployNotified bool `xml:"isAutoUndeployNotified,attr,omitempty"` + AutoDeleteNotified bool `xml:"isAutoDeleteNotified,attr,omitempty"` + IsComputePolicyCompliant bool `xml:"isComputePolicyCompliant,attr,omitempty"` + VmSizingPolicyId string `xml:"vmSizingPolicyId,attr,omitempty"` + VmPlacementPolicyId string `xml:"vmPlacementPolicyId,attr,omitempty"` + Encrypted bool `xml:"encrypted,attr,omitempty"` + DateCreated string `xml:"dateCreated,attr,omitempty"` + TotalStorageAllocatedMb string `xml:"totalStorageAllocatedMb,attr,omitempty"` + IsExpired bool `xml:"isExpired,attr,omitempty"` + Link []*Link `xml:"Link,omitempty"` + MetaData *Metadata `xml:"Metadata,omitempty"` } // QueryResultVAppRecordType represents a VM record as query result. type QueryResultVAppRecordType struct { // Attributes - HREF string `xml:"href,attr,omitempty"` // The URI of the entity. - Name string `xml:"name,attr"` // The name of the entity. - CreationDate string `xml:"creationDate,attr,omitempty"` // Creation date/time of the vApp. - Busy bool `xml:"isBusy,attr,omitempty"` - Deployed bool `xml:"isDeployed,attr,omitempty"` // True if the vApp is deployed. - Enabled bool `xml:"isEnabled,attr,omitempty"` - Expired bool `xml:"isExpired,attr,omitempty"` - MaintenanceMode bool `xml:"isInMaintenanceMode,attr,omitempty"` - Public bool `xml:"isPublic,attr,omitempty"` - OwnerName string `xml:"ownerName,attr,omitempty"` - Status string `xml:"status,attr,omitempty"` - VdcHREF string `xml:"vdc,attr,omitempty"` - VdcName string `xml:"vdcName,attr,omitempty"` - NumberOfVMs int `xml:"numberOfVMs,attr,omitempty"` - NumberOfCPUs int `xml:"numberOfCpus,attr,omitempty"` - CpuAllocationMhz int `xml:"cpuAllocationMhz,attr,omitempty"` - CpuAllocationInMhz int `xml:"cpuAllocationInMhz,attr,omitempty"` - StorageKB int `xml:"storageKB,attr,omitempty"` - MemoryAllocationMB int `xml:"memoryAllocationMB,attr,omitempty"` - AutoDeleteNotified bool `xml:"isAutoDeleteNotified,attr,omitempty"` - AutoUndeployNotified bool `xml:"isAutoUndeployNotified,attr,omitempty"` - VdcEnabled bool `xml:"isVdcEnabled,attr,omitempty"` - HonorBootOrder bool `xml:"honorBookOrder,attr,omitempty"` - HighestSupportedVersion int `xml:"pvdcHighestSupportedHardwareVersion,attr,omitempty"` - LowestHardwareVersion int `xml:"lowestHardwareVersionInVApp,attr,omitempty"` - TaskHREF string `xml:"task,attr,omitempty"` - TaskStatusName string `xml:"taskStatusName,attr,omitempty"` - TaskStatus string `xml:"TaskStatus,attr,omitempty"` - TaskDetails string `xml:"taskDetails,attr,omitempty"` + HREF string `xml:"href,attr,omitempty"` // The URI of the entity. + Name string `xml:"name,attr"` // The name of the entity. + CreationDate string `xml:"creationDate,attr,omitempty"` // Creation date/time of the vApp. + Busy bool `xml:"isBusy,attr,omitempty"` + Deployed bool `xml:"isDeployed,attr,omitempty"` // True if the vApp is deployed. + Enabled bool `xml:"isEnabled,attr,omitempty"` + Expired bool `xml:"isExpired,attr,omitempty"` + MaintenanceMode bool `xml:"isInMaintenanceMode,attr,omitempty"` + Public bool `xml:"isPublic,attr,omitempty"` + OwnerName string `xml:"ownerName,attr,omitempty"` + Status string `xml:"status,attr,omitempty"` + VdcHREF string `xml:"vdc,attr,omitempty"` + VdcName string `xml:"vdcName,attr,omitempty"` + NumberOfVMs int `xml:"numberOfVMs,attr,omitempty"` + NumberOfCPUs int `xml:"numberOfCpus,attr,omitempty"` + CpuAllocationMhz int `xml:"cpuAllocationMhz,attr,omitempty"` + CpuAllocationInMhz int `xml:"cpuAllocationInMhz,attr,omitempty"` + StorageKB int `xml:"storageKB,attr,omitempty"` + MemoryAllocationMB int `xml:"memoryAllocationMB,attr,omitempty"` + AutoDeleteNotified bool `xml:"isAutoDeleteNotified,attr,omitempty"` + AutoUndeployNotified bool `xml:"isAutoUndeployNotified,attr,omitempty"` + VdcEnabled bool `xml:"isVdcEnabled,attr,omitempty"` + HonorBootOrder bool `xml:"honorBookOrder,attr,omitempty"` + HighestSupportedVersion int `xml:"pvdcHighestSupportedHardwareVersion,attr,omitempty"` + LowestHardwareVersion int `xml:"lowestHardwareVersionInVApp,attr,omitempty"` + TaskHREF string `xml:"task,attr,omitempty"` + TaskStatusName string `xml:"taskStatusName,attr,omitempty"` + TaskStatus string `xml:"TaskStatus,attr,omitempty"` + TaskDetails string `xml:"taskDetails,attr,omitempty"` + MetaData *Metadata `xml:"Metadata,omitempty"` } // QueryResultOrgVdcStorageProfileRecordType represents a storage @@ -2812,3 +2821,39 @@ type VmAffinityRules struct { Link *Link `xml:"Link,omitempty"` // VmAffinityRule []*VmAffinityRule `xml:"VmAffinityRule,omitempty"` } + +// ControlAccessParams specifies access controls for a resource. +type ControlAccessParams struct { + XMLName xml.Name `xml:"ControlAccessParams"` + Xmlns string `xml:"xmlns,attr"` + IsSharedToEveryone bool `xml:"IsSharedToEveryone"` // If true, the resource is shared with everyone in the organization. Defaults to false. + EveryoneAccessLevel *string `xml:"EveryoneAccessLevel,omitempty"` // If IsSharedToEveryone is true, this element must be present to specify the access level. for all members of the organization. One of: FullControl Change ReadOnly + AccessSettings *AccessSettingList `xml:"AccessSettings,omitempty"` // The access settings to be applied if IsSharedToEveryone is false. Required on create and modify if IsSharedToEveryone is false. +} + +// AccessSettingList is a tagged list of AccessSetting +type AccessSettingList struct { + AccessSetting []*AccessSetting `xml:"AccessSetting"` +} + +// LocalSubject is the user, group, or organization to which control access settings apply. +type LocalSubject struct { + HREF string `xml:"href,attr"` // Required - The URL with the full identification of the subject + Name string `xml:"name,attr"` // The name of the subject. Not needed in input, but it is returned on reading + Type string `xml:"type,attr"` // Required - The MIME type of the subject. So far, we are using users, groups, and organizations +} + +// AccessSetting controls access to the resource. +type AccessSetting struct { + XMLName xml.Name `xml:"AccessSetting"` + Subject *LocalSubject `xml:"Subject,omitempty"` // The user or group to which these settings apply. + ExternalSubject *ExternalSubject `xml:"ExternalSubject,omitempty"` // Subject existing external of VCD, to which these settings apply. + AccessLevel string `xml:"AccessLevel"` // The access level for the subject. One of: FullControl Change ReadOnly Deny (only for a VDC resource) +} + +// ExternalSubjectType is a reference to a user or group managed by an identity provider configured for use in this organization. +type ExternalSubject struct { + IdpType string `xml:"IdpType"` // The type of identity provider for example: OAUTH, SAML, LDAP etc for this SubjectID. + IsUser bool `xml:"IsUser"` // If true, SubjectID is a reference to a user defined by this organization's identity provider. If false or empty, SubjectID is a reference to a group defined by this organization's identity provider. + SubjectId string `xml:"SubjectId"` // The primary key that your identity provider uses to uniquely identify the user or group referenced in SubjectId. +}