From 83aac1cb08d888ac73d41f2ecad0850e72388a97 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Fri, 17 Mar 2023 09:10:12 +0100 Subject: [PATCH 01/19] Add types needed for read-only catalog sharing Signed-off-by: Giuseppe Maxia --- types/v56/constants.go | 2 ++ types/v56/types.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/types/v56/constants.go b/types/v56/constants.go index 47b23ed21..bd59d580e 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -137,6 +137,8 @@ const ( MimeLeaseSettingSection = "application/vnd.vmware.vcloud.leaseSettingsSection+xml" // Mime to publish external catalog PublishExternalCatalog = "application/vnd.vmware.admin.publishExternalCatalogParams+xml" + // Mime to publish a catalog + PublishCatalog = "application/vnd.vmware.admin.publishCatalogParams+xml" // Mime to subscribe to an external catalog MimeSubscribeToExternalCatalog = "application/vnd.vmware.admin.externalCatalogSubscriptionParams+json" // Mime to identify a media item diff --git a/types/v56/types.go b/types/v56/types.go index e814ba36d..014c9c2cb 100644 --- a/types/v56/types.go +++ b/types/v56/types.go @@ -1134,6 +1134,14 @@ type PublishExternalCatalogParams struct { PreserveIdentityInfoFlag *bool `xml:"PreserveIdentityInfoFlag,omitempty"` // True includes BIOS UUIDs and MAC addresses in the downloaded OVF package. If false, those information will be excluded. } +// PublishCatalogParams represents the configuration parameters of a catalog published to other orgs +// It is used in conjunction with the "IsPublished" state of the catalog itself +type PublishCatalogParams struct { + XMLName xml.Name `xml:"PublishCatalogParams"` + Xmlns string `xml:"xmlns,attr,omitempty"` + IsPublished *bool `xml:"IsPublished,omitempty"` // True enables publication (read-only access) +} + // ExternalCatalogSubscription represents the configuration parameters for a catalog that has an external subscription // Type: ExternalCatalogSubscriptionParamsType // Namespace: http://www.vmware.com/vcloud/v1.5 From 18324b239c01df2832e6cc4f72792a6837c4d36d Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Fri, 17 Mar 2023 09:10:51 +0100 Subject: [PATCH 02/19] Add catalog parent retrieval to GetCatalogByHref Signed-off-by: Giuseppe Maxia --- govcd/catalog.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/govcd/catalog.go b/govcd/catalog.go index ab65c4974..02c353f86 100644 --- a/govcd/catalog.go +++ b/govcd/catalog.go @@ -1079,12 +1079,12 @@ func (cat *Catalog) PublishToExternalOrganizations(publishExternalCatalog types. return fmt.Errorf("cannot publish to external organization, Object is empty") } - url := cat.Catalog.HREF - if url == "nil" || url == "" { + catalogUrl := cat.Catalog.HREF + if catalogUrl == "nil" || catalogUrl == "" { return fmt.Errorf("cannot publish to external organization, HREF is empty") } - err := publishToExternalOrganizations(cat.client, url, nil, publishExternalCatalog) + err := publishToExternalOrganizations(cat.client, catalogUrl, nil, publishExternalCatalog) if err != nil { return err } @@ -1171,7 +1171,19 @@ func (client *Client) GetCatalogByHref(catalogHref string) (*Catalog, error) { if err != nil { return nil, err } - + // Setting the catalog parent, necessary to handle the tenant context + org := NewOrg(client) + for _, link := range cat.Catalog.Link { + if link.Rel == "up" && link.Type == types.MimeOrg { + _, err = client.ExecuteRequest(link.HREF, http.MethodGet, + "", "error retrieving parent Org: %s", nil, org.Org) + if err != nil { + return nil, fmt.Errorf("error retrieving catalog parent: %s", err) + } + break + } + } + cat.parent = org return cat, nil } From c15eaa5ae69057ad2cd7988be0b5bb55b6a53bdd Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Fri, 17 Mar 2023 09:11:34 +0100 Subject: [PATCH 03/19] Add catalog parent retrieval to GetAdminCatalogByHref Signed-off-by: Giuseppe Maxia --- govcd/admincatalog.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/govcd/admincatalog.go b/govcd/admincatalog.go index a6c60fb8b..9585249c5 100644 --- a/govcd/admincatalog.go +++ b/govcd/admincatalog.go @@ -624,6 +624,20 @@ func (client *Client) GetAdminCatalogByHref(catalogHref string) (*AdminCatalog, return nil, err } + // Setting the catalog parent, necessary to handle the tenant context + org := NewAdminOrg(client) + for _, link := range cat.AdminCatalog.Link { + if link.Rel == "up" && link.Type == types.MimeAdminOrg { + _, err = client.ExecuteRequest(link.HREF, http.MethodGet, + "", "error retrieving parent Org: %s", nil, org.AdminOrg) + if err != nil { + return nil, fmt.Errorf("error retrieving catalog parent: %s", err) + } + break + } + } + + cat.parent = org return cat, nil } From d24dcf3e62f6729e87056950fc10b6444af4f960 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Fri, 17 Mar 2023 09:11:56 +0100 Subject: [PATCH 04/19] Add functions to handle read-only catalog sharing * AdminCatalog.SetReadOnlyAccessControl * Catalog.SetReadOnlyAccessControl * AdminCatalog.IsSharedReadOnly * Catalog.IsSharedReadOnly Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 141 ++++++++++++++++++++++++++++++++++++ govcd/catalog_test.go | 156 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) diff --git a/govcd/access_control.go b/govcd/access_control.go index 4e263c75c..96ce2849b 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -456,3 +456,144 @@ func checkSanityVdcControlAccess(vdc *Vdc) error { } return nil } + +func publishCatalog(client *Client, catalogUrl string, tenantContext *TenantContext, publishCatalog types.PublishCatalogParams) error { + catalogUrl = catalogUrl + "/action/publish" + + publishCatalog.Xmlns = types.XMLNamespaceVCloud + + if tenantContext != nil { + client.SetCustomHeader(getTenantContextHeader(tenantContext)) + } + + err := client.ExecuteRequestWithoutResponse(catalogUrl, http.MethodPost, + types.PublishCatalog, "error publishing catalog: %s", publishCatalog) + + if tenantContext != nil { + client.RemoveProvidedCustomHeaders(getTenantContextHeader(tenantContext)) + } + + return err +} + +// publish publishes a catalog read-only access control to all organizations +// This operation is usually the second step for a read-only sharing to all Orgs +func (cat *Catalog) publish(isPublished bool) error { + if cat.Catalog == nil { + return fmt.Errorf("cannot publish catalog, Object is empty") + } + + catalogUrl := cat.Catalog.HREF + if catalogUrl == "nil" || catalogUrl == "" { + return fmt.Errorf("cannot publish catalog, HREF is empty") + } + + tenantContext, err := cat.getTenantContext() + if err != nil { + return fmt.Errorf("cannot publish catalog, tenant context error: %s", err) + } + + publishParameters := types.PublishCatalogParams{ + IsPublished: takeBoolPointer(isPublished), + } + err = publishCatalog(cat.client, catalogUrl, tenantContext, publishParameters) + if err != nil { + return err + } + + return cat.Refresh() +} + +// SetReadOnlyAccessControl will create or rescind the read-only catalog sharing to all organizations +func (cat *Catalog) SetReadOnlyAccessControl(isPublished bool) error { + if cat.Catalog == nil { + return fmt.Errorf("cannot set access control, Object is empty") + } + err := cat.SetAccessControl(&types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadOnly), + }, true) + if err != nil { + return err + } + return cat.publish(isPublished) +} + +// IsSharedReadOnly returns the state of the catalog read-only sharing to all organizations +func (cat *Catalog) IsSharedReadOnly() (bool, error) { + accessControl, err := cat.GetAccessControl(true) + if err != nil { + return false, err + } + if accessControl.AccessSettings != nil || accessControl.IsSharedToEveryone { + return false, nil + } + err = cat.Refresh() + if err != nil { + return false, err + } + return cat.Catalog.IsPublished, nil +} + +// IsSharedReadOnly returns the state of the catalog read-only sharing to all organizations +func (cat *AdminCatalog) IsSharedReadOnly() (bool, error) { + accessControl, err := cat.GetAccessControl(true) + if err != nil { + return false, err + } + if accessControl.AccessSettings != nil || accessControl.IsSharedToEveryone { + return false, nil + } + err = cat.Refresh() + if err != nil { + return false, err + } + return cat.AdminCatalog.IsPublished, nil +} + +// publish publishes a catalog read-only access control to all organizations +// This operation is usually the second step for a read-only sharing to all Orgs +func (cat *AdminCatalog) publish(isPublished bool) error { + if cat.AdminCatalog == nil { + return fmt.Errorf("cannot publish catalog, Object is empty") + } + + catalogUrl := cat.AdminCatalog.HREF + if catalogUrl == "nil" || catalogUrl == "" { + return fmt.Errorf("cannot publish catalog, HREF is empty") + } + + tenantContext, err := cat.getTenantContext() + if err != nil { + return fmt.Errorf("cannot publish catalog, tenant context error: %s", err) + } + + publishParameters := types.PublishCatalogParams{ + IsPublished: takeBoolPointer(isPublished), + } + err = publishCatalog(cat.client, catalogUrl, tenantContext, publishParameters) + if err != nil { + return err + } + + err = cat.Refresh() + if err != nil { + return err + } + + return err +} + +func (cat *AdminCatalog) SetReadOnlyAccessControl(isPublished bool) error { + if cat.AdminCatalog == nil { + return fmt.Errorf("cannot set access control, Object is empty") + } + err := cat.SetAccessControl(&types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadOnly), + }, true) + if err != nil { + return err + } + return cat.publish(isPublished) +} diff --git a/govcd/catalog_test.go b/govcd/catalog_test.go index a3145fc05..c16394bef 100644 --- a/govcd/catalog_test.go +++ b/govcd/catalog_test.go @@ -1305,3 +1305,159 @@ func (vcd *TestVCD) Test_CatalogAccessAsOrgUsers(check *C) { } check.Assert(err, IsNil) } + +func (vcd *TestVCD) Test_CatalogAccessAsOrgUsersReadOnly(check *C) { + if vcd.config.Tenants == nil || len(vcd.config.Tenants) < 2 { + check.Skip("no tenants found in configuration") + } + + if vcd.config.OVA.OvaPath == "" || vcd.config.Media.MediaPath == "" { + check.Skip("no OVA or Media path found in configuration") + } + + org1Name := vcd.config.Tenants[0].SysOrg + user1Name := vcd.config.Tenants[0].User + password1 := vcd.config.Tenants[0].Password + org2Name := vcd.config.Tenants[1].SysOrg + user2Name := vcd.config.Tenants[1].User + password2 := vcd.config.Tenants[1].Password + + vcdClient1 := NewVCDClient(vcd.client.Client.VCDHREF, true) + err := vcdClient1.Authenticate(user1Name, password1, org1Name) + check.Assert(err, IsNil) + + vcdClient2 := NewVCDClient(vcd.client.Client.VCDHREF, true) + err = vcdClient2.Authenticate(user2Name, password2, org2Name) + check.Assert(err, IsNil) + + org1, err := vcdClient1.GetAdminOrgByName(org1Name) + check.Assert(err, IsNil) + org2, err := vcdClient2.GetAdminOrgByName(org2Name) + check.Assert(err, IsNil) + check.Assert(org2, NotNil) + catalogName := check.TestName() + "-cat" + fmt.Printf("creating catalog %s in org %s\n", catalogName, org1Name) + adminCatalog1Created, err := org1.CreateCatalog(catalogName, fmt.Sprintf("catalog %s created in %s", catalogName, org1Name)) + check.Assert(err, IsNil) + AddToCleanupList(catalogName, "catalog", org1Name, check.TestName()) + catalog1AsOrg1, err := org1.GetCatalogByName(catalogName, true) + check.Assert(err, IsNil) + fmt.Printf("sharing catalog %s from org %s\n", catalogName, org1Name) + + err = adminCatalog1Created.SetReadOnlyAccessControl(true) + + check.Assert(err, IsNil) + + // populate the catalog + + vappTemplateName := check.TestName() + "-template" + mediaName := check.TestName() + "-media" + fmt.Printf("uploading vApp template into catalog %s\n", catalogName) + task, err := catalog1AsOrg1.UploadOvf(vcd.config.OVA.OvaPath, vappTemplateName, vappTemplateName, 1024) + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + + fmt.Printf("uploading media image into catalog %s\n", catalogName) + uploadTask, err := catalog1AsOrg1.UploadMediaImage(mediaName, "upload from test", vcd.config.Media.MediaPath, 1024) + check.Assert(err, IsNil) + err = uploadTask.WaitTaskCompletion() + check.Assert(err, IsNil) + + vAppTemplateAsSystem, err := catalog1AsOrg1.GetVAppTemplateByName(vappTemplateName) + check.Assert(err, IsNil) + check.Assert(vAppTemplateAsSystem, NotNil) + mediaRecordAsSystem, err := catalog1AsOrg1.GetMediaByName(mediaName, true) + check.Assert(err, IsNil) + check.Assert(mediaRecordAsSystem, NotNil) + + // Retrieve catalog by ID in its own Org + adminCatalog1, err := vcdClient1.Client.GetAdminCatalogById(adminCatalog1Created.AdminCatalog.ID) + check.Assert(err, IsNil) + check.Assert(adminCatalog1.AdminCatalog.HREF, Equals, adminCatalog1Created.AdminCatalog.HREF) + + catalog1, err := vcdClient1.Client.GetCatalogById(adminCatalog1Created.AdminCatalog.ID) + check.Assert(err, IsNil) + check.Assert(catalog1.Catalog.HREF, Equals, catalog1AsOrg1.Catalog.HREF) + + startTime := time.Now() + timeout := 100 * time.Second + var timeElapsedToAvailability time.Duration + // Start retrieving catalog in the other org + fmt.Printf("retrieving catalog %s in org %s\n", catalogName, org2Name) + for time.Since(startTime) < timeout { + _, err = vcdClient2.Client.GetAdminCatalogById(adminCatalog1Created.AdminCatalog.ID) + if err == nil { + timeElapsedToAvailability = time.Since(startTime) + fmt.Printf("shared catalog available in %s\n", timeElapsedToAvailability) + break + } + time.Sleep(10 * time.Millisecond) + } + // Retrieve the shared catalog in the other organization + adminCatalog2, err := vcdClient2.Client.GetAdminCatalogById(adminCatalog1Created.AdminCatalog.ID) + check.Assert(err, IsNil) + check.Assert(adminCatalog2, NotNil) + + // Retrieve the catalog from both tenants, using functions that don't rely on organization internals + catalog1FromOrg, err := vcdClient1.Client.GetCatalogByName(org1.AdminOrg.Name, catalogName) + check.Assert(err, IsNil) + adminCatalog1FromOrg, err := vcdClient1.Client.GetAdminCatalogByName(org1.AdminOrg.Name, catalogName) + check.Assert(err, IsNil) + catalog2FromOrg, err := vcdClient2.Client.GetCatalogByName(org1.AdminOrg.Name, catalogName) + check.Assert(err, IsNil) + adminCatalog2FromOrg, err := vcdClient2.Client.GetAdminCatalogByName(org1.AdminOrg.Name, catalogName) + check.Assert(err, IsNil) + + // Also retrieve the catalog items from both tenants + vAppTemplate1, err := catalog1FromOrg.GetVAppTemplateByName(vappTemplateName) + check.Assert(err, IsNil) + check.Assert(vAppTemplate1.VAppTemplate.HREF, Equals, vAppTemplateAsSystem.VAppTemplate.HREF) + mediaRecord1, err := catalog1FromOrg.GetMediaByName(mediaName, false) + check.Assert(err, IsNil) + check.Assert(mediaRecord1.Media.HREF, Equals, mediaRecordAsSystem.Media.HREF) + + vAppTemplate2, err := catalog2FromOrg.GetVAppTemplateByName(vappTemplateName) + check.Assert(err, IsNil) + check.Assert(vAppTemplate2.VAppTemplate.HREF, Equals, vAppTemplateAsSystem.VAppTemplate.HREF) + mediaRecord2, err := catalog2FromOrg.GetMediaByName(mediaName, false) + check.Assert(err, IsNil) + check.Assert(mediaRecord2.Media.HREF, Equals, mediaRecordAsSystem.Media.HREF) + + check.Assert(catalog1FromOrg.Catalog.HREF, Equals, catalog1AsOrg1.Catalog.HREF) + check.Assert(adminCatalog1FromOrg.AdminCatalog.HREF, Equals, adminCatalog1Created.AdminCatalog.HREF) + check.Assert(adminCatalog2FromOrg.AdminCatalog.HREF, Equals, adminCatalog1Created.AdminCatalog.HREF) + check.Assert(catalog2FromOrg.Catalog.HREF, Equals, catalog1AsOrg1.Catalog.HREF) + + isSharedReadOnly, err := adminCatalog1.IsSharedReadOnly() + check.Assert(err, IsNil) + check.Assert(isSharedReadOnly, Equals, true) + + fmt.Println("removing read-only catalog sharing") + err = adminCatalog1Created.SetReadOnlyAccessControl(false) + check.Assert(err, IsNil) + catalog1FromOrg, err = vcdClient1.Client.GetCatalogByName(org1.AdminOrg.Name, catalogName) + check.Assert(err, IsNil) + check.Assert(catalog1FromOrg, NotNil) + fmt.Println("try retrieving read-only catalog from second org") + time.Sleep(timeElapsedToAvailability) + adminCatalog2FromOrg, err = vcdClient2.Client.GetAdminCatalogByName(org1.AdminOrg.Name, catalogName) + check.Assert(err, NotNil) + check.Assert(adminCatalog2FromOrg, IsNil) + + isSharedReadOnly, err = adminCatalog1.IsSharedReadOnly() + check.Assert(err, IsNil) + check.Assert(isSharedReadOnly, Equals, false) + + timeout = 30 * time.Second + startTime = time.Now() + for time.Since(startTime) < timeout { + err = adminCatalog1Created.Delete(true, true) + if err == nil { + fmt.Printf("shared catalog deleted in %s\n", time.Since(startTime)) + break + } + time.Sleep(200 * time.Millisecond) + } + check.Assert(err, IsNil) +} From 0996b09706e0926209686f321cacdedcd2f6ee32 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Fri, 17 Mar 2023 09:31:11 +0100 Subject: [PATCH 05/19] Add changelog entries Signed-off-by: Giuseppe Maxia --- .changes/v2.20.0/559-features.md | 1 + .changes/v2.20.0/559-improvements.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 .changes/v2.20.0/559-features.md create mode 100644 .changes/v2.20.0/559-improvements.md diff --git a/.changes/v2.20.0/559-features.md b/.changes/v2.20.0/559-features.md new file mode 100644 index 000000000..4d1fa23d7 --- /dev/null +++ b/.changes/v2.20.0/559-features.md @@ -0,0 +1 @@ +* Added methods `SetReadOnlyAccessControl` and `IsSharedReadOnly` `Catalog` and `AdminCatalog`, to handle read-only catalog sharing [GH-559] diff --git a/.changes/v2.20.0/559-improvements.md b/.changes/v2.20.0/559-improvements.md new file mode 100644 index 000000000..64a388d23 --- /dev/null +++ b/.changes/v2.20.0/559-improvements.md @@ -0,0 +1 @@ +* Added catalog parent retrieval to `client.GetCatalogByHref` and `client.GetAdminCatalogByHref` to facilitate tenant context handling [GH-559] From 3c9000ade3af461357e6bad114bd878842df21a9 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Tue, 21 Mar 2023 17:04:31 +0100 Subject: [PATCH 06/19] Improve error messages Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 2 +- govcd/catalog.go | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/govcd/access_control.go b/govcd/access_control.go index 96ce2849b..4e93f6898 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -514,7 +514,7 @@ func (cat *Catalog) SetReadOnlyAccessControl(isPublished bool) error { EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadOnly), }, true) if err != nil { - return err + return fmt.Errorf("error resetting access control record for catalog %s: %s", cat.Catalog.Name, err) } return cat.publish(isPublished) } diff --git a/govcd/catalog.go b/govcd/catalog.go index 02c353f86..21c9af045 100644 --- a/govcd/catalog.go +++ b/govcd/catalog.go @@ -59,7 +59,7 @@ func (catalog *Catalog) Delete(force, recursive bool) error { // A catalog cannot be removed if it has active tasks, or if any of its items have active tasks err = catalog.consumeTasks() if err != nil { - return err + return fmt.Errorf("error while consuming tasks from catalog %s: %s", catalog.Catalog.Name, err) } } @@ -94,7 +94,7 @@ func (catalog *Catalog) consumeTasks() error { "status": "running,preRunning,queued", }) if err != nil { - return err + return fmt.Errorf("error getting task list from catalog %s: %s", catalog.Catalog.Name, err) } var taskList []string addTask := func(status, href string) { @@ -119,7 +119,7 @@ func (catalog *Catalog) consumeTasks() error { } catalogItemRefs, err := catalog.QueryCatalogItemList() if err != nil { - return err + return fmt.Errorf("error getting catalog %s items list: %s", catalog.Catalog.Name, err) } for _, task := range allTasks { for _, ref := range catalogItemRefs { @@ -131,7 +131,10 @@ func (catalog *Catalog) consumeTasks() error { } } _, err = catalog.client.WaitTaskListCompletion(taskList, true) - return err + if err != nil { + return fmt.Errorf("error while waiting for task list completion for catalog %s: %s", catalog.Catalog.Name, err) + } + return nil } // Envelope is a ovf description root element. File contains information for vmdk files. From 4069f10bba483c3653a337548b0da3926b2bf749 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Tue, 18 Jul 2023 22:41:00 +0200 Subject: [PATCH 07/19] Add method catalog.WaitForTasks Signed-off-by: Giuseppe Maxia --- govcd/catalog.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/govcd/catalog.go b/govcd/catalog.go index ab65c4974..c3fc9fe63 100644 --- a/govcd/catalog.go +++ b/govcd/catalog.go @@ -1205,3 +1205,18 @@ func (client *Client) GetCatalogByName(parentOrg, catalogName string) (*Catalog, } return nil, fmt.Errorf("no catalog '%s' found in Org %s%s", catalogName, parentOrg, parents) } + +// WaitForTasks waits for the catalog's tasks to complete +func (cat *Catalog) WaitForTasks() error { + if ResourceInProgress(cat.Catalog.Tasks) { + err := WaitResource(func() (*types.TasksInProgress, error) { + err := cat.Refresh() + if err != nil { + return nil, err + } + return cat.Catalog.Tasks, nil + }) + return err + } + return nil +} From d5ab5fd171923b62fb0a1e98edf278d2c2ddacaa Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Tue, 18 Jul 2023 22:41:39 +0200 Subject: [PATCH 08/19] Add WaitForTasks to catalog creation Signed-off-by: Giuseppe Maxia --- govcd/adminorg.go | 12 +++++++++++- govcd/org.go | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/govcd/adminorg.go b/govcd/adminorg.go index 0b58ccf9b..eb4862c90 100644 --- a/govcd/adminorg.go +++ b/govcd/adminorg.go @@ -35,7 +35,7 @@ func NewAdminOrg(cli *Client) *AdminOrg { } } -// CreateCatalog creates a catalog with given name and description under the +// CreateCatalog creates a catalog with given name and description under // the given organization. Returns an AdminCatalog that contains a creation // task. // API Documentation: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/POST-CreateCatalog.html @@ -45,6 +45,16 @@ func (adminOrg *AdminOrg) CreateCatalog(name, description string) (AdminCatalog, return AdminCatalog{}, err } adminCatalog.parent = adminOrg + + err = adminCatalog.Refresh() + if err != nil { + return AdminCatalog{}, err + } + // Make sure that the creation task is finished + err = adminCatalog.WaitForTasks() + if err != nil { + return AdminCatalog{}, err + } return *adminCatalog, nil } diff --git a/govcd/org.go b/govcd/org.go index cd1d1963d..c87bde837 100644 --- a/govcd/org.go +++ b/govcd/org.go @@ -143,6 +143,17 @@ func (org *Org) CreateCatalog(name, description string) (Catalog, error) { if err != nil { return Catalog{}, err } + catalog.parent = org + + err = catalog.Refresh() + if err != nil { + return Catalog{}, err + } + // Make sure that the creation task is finished + err = catalog.WaitForTasks() + if err != nil { + return Catalog{}, err + } return *catalog, nil } From 09df34ccab657877fc4c420acb835d820f9d73d6 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Tue, 18 Jul 2023 22:46:13 +0200 Subject: [PATCH 09/19] Add test for catalog creation completeness Signed-off-by: Giuseppe Maxia --- govcd/catalog_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/govcd/catalog_test.go b/govcd/catalog_test.go index 49a9ffd99..717f355a4 100644 --- a/govcd/catalog_test.go +++ b/govcd/catalog_test.go @@ -1304,3 +1304,31 @@ func (vcd *TestVCD) Test_CatalogAccessAsOrgUsers(check *C) { } check.Assert(err, IsNil) } + +func (vcd *TestVCD) Test_CatalogCreateCompleteness(check *C) { + fmt.Printf("Running: %s\n", check.TestName()) + + adminOrg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + check.Assert(adminOrg, NotNil) + catalogName := "TestAdminCatalogCreate" + adminCatalog, err := adminOrg.CreateCatalog(catalogName, catalogName) + check.Assert(err, IsNil) + AddToCleanupList(catalogName, "catalog", vcd.config.VCD.Org, check.TestName()) + metadataLink := adminCatalog.AdminCatalog.Link.ForType(types.MimeMetaData, "add") + check.Assert(metadataLink, NotNil) + err = adminCatalog.Delete(true, true) + check.Assert(err, IsNil) + + catalogName = "TestCatalogCreate" + org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + catalog, err := org.CreateCatalog(catalogName, catalogName) + check.Assert(err, IsNil) + AddToCleanupList(catalogName, "catalog", vcd.config.VCD.Org, check.TestName()) + metadataLink = nil + metadataLink = catalog.Catalog.Link.ForType(types.MimeMetaData, "add") + check.Assert(metadataLink, NotNil) + err = catalog.Delete(true, true) + check.Assert(err, IsNil) +} From 3089c6c7f071cb1705db907c635c8781c4f34521 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Thu, 20 Jul 2023 14:54:45 +0200 Subject: [PATCH 10/19] Fix merge issues Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/govcd/access_control.go b/govcd/access_control.go index 5a87dbf19..c7c5b3e20 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -494,7 +494,7 @@ func (cat *Catalog) publish(isPublished bool) error { } publishParameters := types.PublishCatalogParams{ - IsPublished: takeBoolPointer(isPublished), + IsPublished: &isPublished, } err = publishCatalog(cat.client, catalogUrl, tenantContext, publishParameters) if err != nil { @@ -511,7 +511,7 @@ func (cat *Catalog) SetReadOnlyAccessControl(isPublished bool) error { } err := cat.SetAccessControl(&types.ControlAccessParams{ IsSharedToEveryone: false, - EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadOnly), + EveryoneAccessLevel: addrOf(types.ControlAccessReadOnly), }, true) if err != nil { return fmt.Errorf("error resetting access control record for catalog %s: %s", cat.Catalog.Name, err) @@ -569,7 +569,7 @@ func (cat *AdminCatalog) publish(isPublished bool) error { } publishParameters := types.PublishCatalogParams{ - IsPublished: takeBoolPointer(isPublished), + IsPublished: &isPublished, } err = publishCatalog(cat.client, catalogUrl, tenantContext, publishParameters) if err != nil { @@ -590,7 +590,7 @@ func (cat *AdminCatalog) SetReadOnlyAccessControl(isPublished bool) error { } err := cat.SetAccessControl(&types.ControlAccessParams{ IsSharedToEveryone: false, - EveryoneAccessLevel: takeStringPointer(types.ControlAccessReadOnly), + EveryoneAccessLevel: addrOf(types.ControlAccessReadOnly), }, true) if err != nil { return err From 6300ceee66ef3bd6becdc2d8d06b3fddda2b1599 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Mon, 31 Jul 2023 11:58:57 +0200 Subject: [PATCH 11/19] Improve publishCatalog error message Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/govcd/access_control.go b/govcd/access_control.go index c7c5b3e20..c3eec19e1 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -467,7 +467,7 @@ func publishCatalog(client *Client, catalogUrl string, tenantContext *TenantCont } err := client.ExecuteRequestWithoutResponse(catalogUrl, http.MethodPost, - types.PublishCatalog, "error publishing catalog: %s", publishCatalog) + types.PublishCatalog, "error setting catalog publishing state: %s", publishCatalog) if tenantContext != nil { client.RemoveProvidedCustomHeaders(getTenantContextHeader(tenantContext)) From f2aa6b93ab376e675a49b8707eed56a96c4e740e Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Mon, 2 Oct 2023 13:52:08 +0200 Subject: [PATCH 12/19] Move changelog items to current version Signed-off-by: Giuseppe Maxia --- .changes/v2.20.0/559-features.md | 1 - .changes/v2.22.0/559-features.md | 1 + .changes/{v2.20.0 => v2.22.0}/559-improvements.md | 0 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .changes/v2.20.0/559-features.md create mode 100644 .changes/v2.22.0/559-features.md rename .changes/{v2.20.0 => v2.22.0}/559-improvements.md (100%) diff --git a/.changes/v2.20.0/559-features.md b/.changes/v2.20.0/559-features.md deleted file mode 100644 index 4d1fa23d7..000000000 --- a/.changes/v2.20.0/559-features.md +++ /dev/null @@ -1 +0,0 @@ -* Added methods `SetReadOnlyAccessControl` and `IsSharedReadOnly` `Catalog` and `AdminCatalog`, to handle read-only catalog sharing [GH-559] diff --git a/.changes/v2.22.0/559-features.md b/.changes/v2.22.0/559-features.md new file mode 100644 index 000000000..842594751 --- /dev/null +++ b/.changes/v2.22.0/559-features.md @@ -0,0 +1 @@ +* Added methods `SetReadOnlyAccessControl` and `IsSharedReadOnly` for `Catalog` and `AdminCatalog`, to handle read-only catalog sharing [GH-559] diff --git a/.changes/v2.20.0/559-improvements.md b/.changes/v2.22.0/559-improvements.md similarity index 100% rename from .changes/v2.20.0/559-improvements.md rename to .changes/v2.22.0/559-improvements.md From 6be0423a9c5000822cb1a7a4a435525b42b27bb7 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Mon, 2 Oct 2023 13:52:36 +0200 Subject: [PATCH 13/19] Add missing assertion to catalog test Signed-off-by: Giuseppe Maxia --- govcd/catalog_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/govcd/catalog_test.go b/govcd/catalog_test.go index 0f83694b6..26edff55d 100644 --- a/govcd/catalog_test.go +++ b/govcd/catalog_test.go @@ -1460,6 +1460,7 @@ func (vcd *TestVCD) Test_CatalogAccessAsOrgUsersReadOnly(check *C) { } time.Sleep(200 * time.Millisecond) } + check.Assert(err, IsNil) } func (vcd *TestVCD) Test_CatalogCreateCompleteness(check *C) { From 15510eb5c278d6802b933f73fe7e68b7095af219 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Mon, 2 Oct 2023 14:14:41 +0200 Subject: [PATCH 14/19] Rearrange order of publishing functions Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 86 ++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/govcd/access_control.go b/govcd/access_control.go index c3eec19e1..55aaf9584 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -476,49 +476,6 @@ func publishCatalog(client *Client, catalogUrl string, tenantContext *TenantCont return err } -// publish publishes a catalog read-only access control to all organizations -// This operation is usually the second step for a read-only sharing to all Orgs -func (cat *Catalog) publish(isPublished bool) error { - if cat.Catalog == nil { - return fmt.Errorf("cannot publish catalog, Object is empty") - } - - catalogUrl := cat.Catalog.HREF - if catalogUrl == "nil" || catalogUrl == "" { - return fmt.Errorf("cannot publish catalog, HREF is empty") - } - - tenantContext, err := cat.getTenantContext() - if err != nil { - return fmt.Errorf("cannot publish catalog, tenant context error: %s", err) - } - - publishParameters := types.PublishCatalogParams{ - IsPublished: &isPublished, - } - err = publishCatalog(cat.client, catalogUrl, tenantContext, publishParameters) - if err != nil { - return err - } - - return cat.Refresh() -} - -// SetReadOnlyAccessControl will create or rescind the read-only catalog sharing to all organizations -func (cat *Catalog) SetReadOnlyAccessControl(isPublished bool) error { - if cat.Catalog == nil { - return fmt.Errorf("cannot set access control, Object is empty") - } - err := cat.SetAccessControl(&types.ControlAccessParams{ - IsSharedToEveryone: false, - EveryoneAccessLevel: addrOf(types.ControlAccessReadOnly), - }, true) - if err != nil { - return fmt.Errorf("error resetting access control record for catalog %s: %s", cat.Catalog.Name, err) - } - return cat.publish(isPublished) -} - // IsSharedReadOnly returns the state of the catalog read-only sharing to all organizations func (cat *Catalog) IsSharedReadOnly() (bool, error) { accessControl, err := cat.GetAccessControl(true) @@ -551,6 +508,34 @@ func (cat *AdminCatalog) IsSharedReadOnly() (bool, error) { return cat.AdminCatalog.IsPublished, nil } +// publish publishes a catalog read-only access control to all organizations +// This operation is usually the second step for a read-only sharing to all Orgs +func (cat *Catalog) publish(isPublished bool) error { + if cat.Catalog == nil { + return fmt.Errorf("cannot publish catalog, Object is empty") + } + + catalogUrl := cat.Catalog.HREF + if catalogUrl == "nil" || catalogUrl == "" { + return fmt.Errorf("cannot publish catalog, HREF is empty") + } + + tenantContext, err := cat.getTenantContext() + if err != nil { + return fmt.Errorf("cannot publish catalog, tenant context error: %s", err) + } + + publishParameters := types.PublishCatalogParams{ + IsPublished: &isPublished, + } + err = publishCatalog(cat.client, catalogUrl, tenantContext, publishParameters) + if err != nil { + return err + } + + return cat.Refresh() +} + // publish publishes a catalog read-only access control to all organizations // This operation is usually the second step for a read-only sharing to all Orgs func (cat *AdminCatalog) publish(isPublished bool) error { @@ -584,6 +569,21 @@ func (cat *AdminCatalog) publish(isPublished bool) error { return err } +// SetReadOnlyAccessControl will create or rescind the read-only catalog sharing to all organizations +func (cat *Catalog) SetReadOnlyAccessControl(isPublished bool) error { + if cat.Catalog == nil { + return fmt.Errorf("cannot set access control, Object is empty") + } + err := cat.SetAccessControl(&types.ControlAccessParams{ + IsSharedToEveryone: false, + EveryoneAccessLevel: addrOf(types.ControlAccessReadOnly), + }, true) + if err != nil { + return fmt.Errorf("error resetting access control record for catalog %s: %s", cat.Catalog.Name, err) + } + return cat.publish(isPublished) +} + func (cat *AdminCatalog) SetReadOnlyAccessControl(isPublished bool) error { if cat.AdminCatalog == nil { return fmt.Errorf("cannot set access control, Object is empty") From e611c09dace60bc89cbbbbeb932dc3c0931ea566 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Wed, 4 Oct 2023 17:10:30 +0200 Subject: [PATCH 15/19] Add WaitForTasks to catalog creation Signed-off-by: Giuseppe Maxia --- govcd/org.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/govcd/org.go b/govcd/org.go index c87bde837..b3878261f 100644 --- a/govcd/org.go +++ b/govcd/org.go @@ -131,6 +131,10 @@ func CreateCatalogWithStorageProfile(client *Client, links types.LinkList, Name, _, err := client.ExecuteRequest(createOrgLink.HREF, http.MethodPost, "application/vnd.vmware.admin.catalog+xml", "error creating catalog: %s", vcomp, catalog.AdminCatalog) + err = catalog.WaitForTasks() + if err != nil { + return nil, err + } return catalog, err } From 9c3ab5f8a91524abb88106e3073c5956c137aba8 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Wed, 4 Oct 2023 17:10:53 +0200 Subject: [PATCH 16/19] Add WaitForTasks to AdminCatalog creation Signed-off-by: Giuseppe Maxia --- govcd/adminorg.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/govcd/adminorg.go b/govcd/adminorg.go index eb4862c90..943bcea30 100644 --- a/govcd/adminorg.go +++ b/govcd/adminorg.go @@ -55,6 +55,10 @@ func (adminOrg *AdminOrg) CreateCatalog(name, description string) (AdminCatalog, if err != nil { return AdminCatalog{}, err } + err = adminCatalog.WaitForTasks() + if err != nil { + return AdminCatalog{}, err + } return *adminCatalog, nil } From da848102a52930ea7655a66f02b4f58ca31f6dba Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Wed, 4 Oct 2023 17:11:14 +0200 Subject: [PATCH 17/19] Add function docs Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 1 + 1 file changed, 1 insertion(+) diff --git a/govcd/access_control.go b/govcd/access_control.go index 55aaf9584..e61afc5d7 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -584,6 +584,7 @@ func (cat *Catalog) SetReadOnlyAccessControl(isPublished bool) error { return cat.publish(isPublished) } +// SetReadOnlyAccessControl will create or rescind the read-only AdminCatalog sharing to all organizations func (cat *AdminCatalog) SetReadOnlyAccessControl(isPublished bool) error { if cat.AdminCatalog == nil { return fmt.Errorf("cannot set access control, Object is empty") From 9bef8a19e5ba4552923e1ddb1e5d881424b83cb8 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Wed, 4 Oct 2023 17:27:44 +0200 Subject: [PATCH 18/19] Fix missing error check Signed-off-by: Giuseppe Maxia --- govcd/org.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/govcd/org.go b/govcd/org.go index b3878261f..e5a742fc5 100644 --- a/govcd/org.go +++ b/govcd/org.go @@ -130,12 +130,15 @@ func CreateCatalogWithStorageProfile(client *Client, links types.LinkList, Name, catalog := NewAdminCatalog(client) _, err := client.ExecuteRequest(createOrgLink.HREF, http.MethodPost, "application/vnd.vmware.admin.catalog+xml", "error creating catalog: %s", vcomp, catalog.AdminCatalog) + if err != nil { + return nil, err + } err = catalog.WaitForTasks() if err != nil { return nil, err } - return catalog, err + return catalog, nil } // CreateCatalog creates a catalog with given name and description under From e12d571baadec5dfaa44bfa808eb34381c487cc9 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Mon, 9 Oct 2023 16:41:49 +0200 Subject: [PATCH 19/19] Update comments Signed-off-by: Giuseppe Maxia --- govcd/access_control.go | 4 ++-- types/v56/types.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/govcd/access_control.go b/govcd/access_control.go index e61afc5d7..140722e4f 100644 --- a/govcd/access_control.go +++ b/govcd/access_control.go @@ -508,7 +508,7 @@ func (cat *AdminCatalog) IsSharedReadOnly() (bool, error) { return cat.AdminCatalog.IsPublished, nil } -// publish publishes a catalog read-only access control to all organizations +// publish publishes a catalog read-only access control to all organizations. // This operation is usually the second step for a read-only sharing to all Orgs func (cat *Catalog) publish(isPublished bool) error { if cat.Catalog == nil { @@ -536,7 +536,7 @@ func (cat *Catalog) publish(isPublished bool) error { return cat.Refresh() } -// publish publishes a catalog read-only access control to all organizations +// publish publishes a catalog read-only access control to all organizations. // This operation is usually the second step for a read-only sharing to all Orgs func (cat *AdminCatalog) publish(isPublished bool) error { if cat.AdminCatalog == nil { diff --git a/types/v56/types.go b/types/v56/types.go index 12980b2fe..d900ea452 100644 --- a/types/v56/types.go +++ b/types/v56/types.go @@ -1134,7 +1134,7 @@ type PublishExternalCatalogParams struct { PreserveIdentityInfoFlag *bool `xml:"PreserveIdentityInfoFlag,omitempty"` // True includes BIOS UUIDs and MAC addresses in the downloaded OVF package. If false, those information will be excluded. } -// PublishCatalogParams represents the configuration parameters of a catalog published to other orgs +// PublishCatalogParams represents the configuration parameters of a catalog published to other orgs. // It is used in conjunction with the "IsPublished" state of the catalog itself type PublishCatalogParams struct { XMLName xml.Name `xml:"PublishCatalogParams"`