Skip to content

Commit 58f8c31

Browse files
authored
Add CRUD methods that support Runtime Defined Entity (RDE) Types (#545)
Signed-off-by: abarreiro <abarreiro@vmware.com>
1 parent c58ca30 commit 58f8c31

File tree

7 files changed

+461
-0
lines changed

7 files changed

+461
-0
lines changed

.changes/v2.20.0/545-features.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
* Added support for Runtime Defined Entity Types with client methods `VCDClient.CreateRdeType`, `VCDClient.GetAllRdeTypes`,
2+
`VCDClient.GetRdeType`, `VCDClient.GetRdeTypeById` and methods to manipulate them `DefinedEntityType.Update`,
3+
`DefinedEntityType.Delete` [GH-545]

govcd/defined_entity.go

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
3+
*/
4+
5+
package govcd
6+
7+
import (
8+
"fmt"
9+
"github.com/vmware/go-vcloud-director/v2/types/v56"
10+
"net/url"
11+
)
12+
13+
// DefinedEntityType is a type for handling Runtime Defined Entity (RDE) Type definitions.
14+
// Note. Running a few of these operations in parallel may corrupt database in VCD (at least <= 10.4.2)
15+
type DefinedEntityType struct {
16+
DefinedEntityType *types.DefinedEntityType
17+
client *Client
18+
}
19+
20+
// CreateRdeType creates a Runtime Defined Entity Type.
21+
// Only a System administrator can create RDE Types.
22+
func (vcdClient *VCDClient) CreateRdeType(rde *types.DefinedEntityType) (*DefinedEntityType, error) {
23+
client := vcdClient.Client
24+
25+
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntityTypes
26+
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
urlRef, err := client.OpenApiBuildEndpoint(endpoint)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
result := &DefinedEntityType{
37+
DefinedEntityType: &types.DefinedEntityType{},
38+
client: &vcdClient.Client,
39+
}
40+
41+
err = client.OpenApiPostItem(apiVersion, urlRef, nil, rde, result.DefinedEntityType, nil)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
return result, nil
47+
}
48+
49+
// GetAllRdeTypes retrieves all Runtime Defined Entity Types. Query parameters can be supplied to perform additional filtering.
50+
func (vcdClient *VCDClient) GetAllRdeTypes(queryParameters url.Values) ([]*DefinedEntityType, error) {
51+
client := vcdClient.Client
52+
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntityTypes
53+
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
urlRef, err := client.OpenApiBuildEndpoint(endpoint)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
typeResponses := []*types.DefinedEntityType{{}}
64+
err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponses, nil)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
// Wrap all typeResponses into DefinedEntityType types with client
70+
returnRDEs := make([]*DefinedEntityType, len(typeResponses))
71+
for sliceIndex := range typeResponses {
72+
returnRDEs[sliceIndex] = &DefinedEntityType{
73+
DefinedEntityType: typeResponses[sliceIndex],
74+
client: &vcdClient.Client,
75+
}
76+
}
77+
78+
return returnRDEs, nil
79+
}
80+
81+
// GetRdeType gets a Runtime Defined Entity Type by its unique combination of vendor, nss and version.
82+
func (vcdClient *VCDClient) GetRdeType(vendor, nss, version string) (*DefinedEntityType, error) {
83+
queryParameters := url.Values{}
84+
queryParameters.Add("filter", fmt.Sprintf("vendor==%s;nss==%s;version==%s", vendor, nss, version))
85+
rdeTypes, err := vcdClient.GetAllRdeTypes(queryParameters)
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
if len(rdeTypes) == 0 {
91+
return nil, fmt.Errorf("%s could not find the Runtime Defined Entity Type with vendor %s, nss %s and version %s", ErrorEntityNotFound, vendor, nss, version)
92+
}
93+
94+
if len(rdeTypes) > 1 {
95+
return nil, fmt.Errorf("found more than 1 Runtime Defined Entity Type with vendor %s, nss %s and version %s", vendor, nss, version)
96+
}
97+
98+
return rdeTypes[0], nil
99+
}
100+
101+
// GetRdeTypeById gets a Runtime Defined Entity Type by its ID.
102+
func (vcdClient *VCDClient) GetRdeTypeById(id string) (*DefinedEntityType, error) {
103+
client := vcdClient.Client
104+
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntityTypes
105+
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
urlRef, err := client.OpenApiBuildEndpoint(endpoint, id)
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
result := &DefinedEntityType{
116+
DefinedEntityType: &types.DefinedEntityType{},
117+
client: &vcdClient.Client,
118+
}
119+
120+
err = client.OpenApiGetItem(apiVersion, urlRef, nil, result.DefinedEntityType, nil)
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
return result, nil
126+
}
127+
128+
// Update updates the receiver Runtime Defined Entity Type with the values given by the input.
129+
// Only a System administrator can create RDE Types.
130+
func (rdeType *DefinedEntityType) Update(rdeTypeToUpdate types.DefinedEntityType) error {
131+
client := rdeType.client
132+
if rdeType.DefinedEntityType.ID == "" {
133+
return fmt.Errorf("ID of the receiver Runtime Defined Entity Type is empty")
134+
}
135+
136+
if rdeTypeToUpdate.ID != "" && rdeTypeToUpdate.ID != rdeType.DefinedEntityType.ID {
137+
return fmt.Errorf("ID of the receiver Runtime Defined Entity and the input ID don't match")
138+
}
139+
140+
// Name and schema are mandatory, even when we don't want to update them, so we populate them in this situation to avoid errors
141+
// and make this method more user friendly.
142+
if rdeTypeToUpdate.Name == "" {
143+
rdeTypeToUpdate.Name = rdeType.DefinedEntityType.Name
144+
}
145+
if rdeTypeToUpdate.Schema == nil || len(rdeTypeToUpdate.Schema) == 0 {
146+
rdeTypeToUpdate.Schema = rdeType.DefinedEntityType.Schema
147+
}
148+
149+
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntityTypes
150+
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
151+
if err != nil {
152+
return err
153+
}
154+
155+
urlRef, err := client.OpenApiBuildEndpoint(endpoint, rdeType.DefinedEntityType.ID)
156+
if err != nil {
157+
return err
158+
}
159+
160+
err = client.OpenApiPutItem(apiVersion, urlRef, nil, rdeTypeToUpdate, rdeType.DefinedEntityType, nil)
161+
if err != nil {
162+
return err
163+
}
164+
165+
return nil
166+
}
167+
168+
// Delete deletes the receiver Runtime Defined Entity Type.
169+
// Only a System administrator can delete RDE Types.
170+
func (rdeType *DefinedEntityType) Delete() error {
171+
client := rdeType.client
172+
if rdeType.DefinedEntityType.ID == "" {
173+
return fmt.Errorf("ID of the receiver Runtime Defined Entity Type is empty")
174+
}
175+
176+
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRdeEntityTypes
177+
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
178+
if err != nil {
179+
return err
180+
}
181+
182+
urlRef, err := client.OpenApiBuildEndpoint(endpoint, rdeType.DefinedEntityType.ID)
183+
if err != nil {
184+
return err
185+
}
186+
187+
err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
188+
if err != nil {
189+
return err
190+
}
191+
192+
rdeType.DefinedEntityType = &types.DefinedEntityType{}
193+
return nil
194+
}

govcd/defined_entity_test.go

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//go:build functional || openapi || rde || ALL
2+
// +build functional openapi rde ALL
3+
4+
/*
5+
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
6+
*/
7+
8+
package govcd
9+
10+
import (
11+
"encoding/json"
12+
"fmt"
13+
"github.com/vmware/go-vcloud-director/v2/types/v56"
14+
. "gopkg.in/check.v1"
15+
"io"
16+
"os"
17+
"path/filepath"
18+
"strings"
19+
)
20+
21+
// Test_RdeType tests the CRUD operations for the RDE Type with both System administrator and a tenant user.
22+
func (vcd *TestVCD) Test_RdeType(check *C) {
23+
if vcd.skipAdminTests {
24+
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
25+
}
26+
27+
skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointRdeEntityTypes)
28+
if len(vcd.config.Tenants) == 0 {
29+
check.Skip("skipping as there is no configured tenant users")
30+
}
31+
32+
// Creates the clients for the System admin and the Tenant user
33+
systemAdministratorClient := vcd.client
34+
tenantUserClient := NewVCDClient(vcd.client.Client.VCDHREF, true)
35+
err := tenantUserClient.Authenticate(vcd.config.Tenants[0].User, vcd.config.Tenants[0].Password, vcd.config.Tenants[0].SysOrg)
36+
check.Assert(err, IsNil)
37+
38+
unmarshaledRdeTypeSchema, err := loadRdeTypeSchemaFromTestResources()
39+
check.Assert(err, IsNil)
40+
check.Assert(true, Equals, len(unmarshaledRdeTypeSchema) > 0)
41+
42+
// First, it checks how many exist already, as VCD contains some pre-defined ones.
43+
allRdeTypesBySystemAdmin, err := systemAdministratorClient.GetAllRdeTypes(nil)
44+
check.Assert(err, IsNil)
45+
alreadyPresentRdes := len(allRdeTypesBySystemAdmin)
46+
47+
// For the tenant, it returns 0 RDE Types, but no error.
48+
allRdeTypesByTenant, err := tenantUserClient.GetAllRdeTypes(nil)
49+
check.Assert(err, IsNil)
50+
check.Assert(len(allRdeTypesByTenant), Equals, 0)
51+
52+
// Then we create a new RDE Type with System administrator.
53+
// Can't put check.TestName() in nss due to a bug in VCD 10.4.1 that causes RDEs to fail on GET once created with special characters like "."
54+
vendor := "vmware"
55+
nss := strings.ReplaceAll(check.TestName()+"name", ".", "")
56+
version := "1.2.3"
57+
rdeTypeToCreate := &types.DefinedEntityType{
58+
Name: check.TestName(),
59+
Nss: nss,
60+
Version: version,
61+
Description: "Description of " + check.TestName(),
62+
Schema: unmarshaledRdeTypeSchema,
63+
Vendor: vendor,
64+
Interfaces: []string{"urn:vcloud:interface:vmware:k8s:1.0.0"},
65+
}
66+
createdRdeType, err := systemAdministratorClient.CreateRdeType(rdeTypeToCreate)
67+
check.Assert(err, IsNil)
68+
check.Assert(createdRdeType, NotNil)
69+
check.Assert(createdRdeType.DefinedEntityType.Name, Equals, rdeTypeToCreate.Name)
70+
check.Assert(createdRdeType.DefinedEntityType.Nss, Equals, rdeTypeToCreate.Nss)
71+
check.Assert(createdRdeType.DefinedEntityType.Version, Equals, rdeTypeToCreate.Version)
72+
check.Assert(createdRdeType.DefinedEntityType.Schema, NotNil)
73+
check.Assert(createdRdeType.DefinedEntityType.Schema["type"], Equals, "object")
74+
check.Assert(createdRdeType.DefinedEntityType.Schema["definitions"], NotNil)
75+
check.Assert(createdRdeType.DefinedEntityType.Schema["required"], NotNil)
76+
check.Assert(createdRdeType.DefinedEntityType.Schema["properties"], NotNil)
77+
AddToCleanupListOpenApi(createdRdeType.DefinedEntityType.ID, check.TestName(), types.OpenApiPathVersion1_0_0+types.OpenApiEndpointRdeEntityTypes+createdRdeType.DefinedEntityType.ID)
78+
79+
// Tenants can't create RDE Types
80+
nilRdeType, err := tenantUserClient.CreateRdeType(&types.DefinedEntityType{
81+
Name: check.TestName(),
82+
Nss: "notworking",
83+
Version: "4.5.6",
84+
Schema: unmarshaledRdeTypeSchema,
85+
Vendor: "willfail",
86+
})
87+
check.Assert(err, NotNil)
88+
check.Assert(nilRdeType, IsNil)
89+
check.Assert(strings.Contains(err.Error(), "ACCESS_TO_RESOURCE_IS_FORBIDDEN"), Equals, true)
90+
91+
// Assign rights to the tenant user, so it can perform following operations.
92+
// We don't need to clean the rights afterwards as deleting the RDE Type deletes the associated bundle
93+
// with its rights.
94+
role, err := systemAdministratorClient.Client.GetGlobalRoleByName("Organization Administrator")
95+
check.Assert(err, IsNil)
96+
check.Assert(role, NotNil)
97+
98+
rightsBundleName := fmt.Sprintf("%s:%s Entitlement", vendor, nss)
99+
rightsBundle, err := systemAdministratorClient.Client.GetRightsBundleByName(rightsBundleName)
100+
check.Assert(err, IsNil)
101+
check.Assert(rightsBundle, NotNil)
102+
103+
err = rightsBundle.PublishAllTenants()
104+
check.Assert(err, IsNil)
105+
106+
rights, err := rightsBundle.GetRights(nil)
107+
check.Assert(err, IsNil)
108+
check.Assert(len(rights), Not(Equals), 0)
109+
110+
var rightsToAdd []types.OpenApiReference
111+
for _, right := range rights {
112+
if strings.Contains(right.Name, fmt.Sprintf("%s:%s", vendor, nss)) {
113+
rightsToAdd = append(rightsToAdd, types.OpenApiReference{
114+
Name: right.Name,
115+
ID: right.ID,
116+
})
117+
}
118+
}
119+
check.Assert(rightsToAdd, NotNil)
120+
check.Assert(len(rightsToAdd), Not(Equals), 0)
121+
122+
err = role.AddRights(rightsToAdd)
123+
check.Assert(err, IsNil)
124+
125+
// As we created a new RDE Type, we check the new count is correct in both System admin and Tenant user
126+
allRdeTypesBySystemAdmin, err = systemAdministratorClient.GetAllRdeTypes(nil)
127+
check.Assert(err, IsNil)
128+
check.Assert(len(allRdeTypesBySystemAdmin), Equals, alreadyPresentRdes+1)
129+
130+
// Count is 1 for tenant user as it can only retrieve the created type as per the assigned rights above.
131+
allRdeTypesByTenant, err = tenantUserClient.GetAllRdeTypes(nil)
132+
check.Assert(err, IsNil)
133+
check.Assert(len(allRdeTypesByTenant), Equals, 1)
134+
135+
// Test the multiple ways of getting a RDE Types in both users.
136+
obtainedRdeTypeBySysAdmin, err := systemAdministratorClient.GetRdeTypeById(createdRdeType.DefinedEntityType.ID)
137+
check.Assert(err, IsNil)
138+
check.Assert(*obtainedRdeTypeBySysAdmin.DefinedEntityType, DeepEquals, *createdRdeType.DefinedEntityType)
139+
140+
// The RDE Type retrieved by the tenant should be the same as the retrieved by Sysadmin
141+
obtainedRdeTypeByTenant, err := tenantUserClient.GetRdeTypeById(createdRdeType.DefinedEntityType.ID)
142+
check.Assert(err, IsNil)
143+
check.Assert(*obtainedRdeTypeByTenant.DefinedEntityType, DeepEquals, *obtainedRdeTypeBySysAdmin.DefinedEntityType)
144+
145+
obtainedRdeTypeBySysAdmin, err = systemAdministratorClient.GetRdeType(createdRdeType.DefinedEntityType.Vendor, createdRdeType.DefinedEntityType.Nss, createdRdeType.DefinedEntityType.Version)
146+
check.Assert(err, IsNil)
147+
check.Assert(*obtainedRdeTypeBySysAdmin.DefinedEntityType, DeepEquals, *obtainedRdeTypeBySysAdmin.DefinedEntityType)
148+
149+
// The RDE Type retrieved by the tenant should be the same as the retrieved by Sysadmin
150+
obtainedRdeTypeByTenant, err = tenantUserClient.GetRdeType(createdRdeType.DefinedEntityType.Vendor, createdRdeType.DefinedEntityType.Nss, createdRdeType.DefinedEntityType.Version)
151+
check.Assert(err, IsNil)
152+
check.Assert(*obtainedRdeTypeByTenant.DefinedEntityType, DeepEquals, *obtainedRdeTypeBySysAdmin.DefinedEntityType)
153+
154+
// We don't want to update the name nor the schema. It should populate them from the receiver object automatically
155+
err = obtainedRdeTypeBySysAdmin.Update(types.DefinedEntityType{
156+
Description: rdeTypeToCreate.Description + "UpdatedByAdmin",
157+
})
158+
check.Assert(err, IsNil)
159+
check.Assert(obtainedRdeTypeBySysAdmin.DefinedEntityType.Description, Equals, rdeTypeToCreate.Description+"UpdatedByAdmin")
160+
161+
// We delete it with Sysadmin
162+
deletedId := createdRdeType.DefinedEntityType.ID
163+
err = createdRdeType.Delete()
164+
check.Assert(err, IsNil)
165+
check.Assert(*createdRdeType.DefinedEntityType, DeepEquals, types.DefinedEntityType{})
166+
167+
_, err = systemAdministratorClient.GetRdeTypeById(deletedId)
168+
check.Assert(err, NotNil)
169+
check.Assert(strings.Contains(err.Error(), ErrorEntityNotFound.Error()), Equals, true)
170+
}
171+
172+
// loadRdeTypeSchemaFromTestResources loads the RDE schema present in the test-resources folder and unmarshals it
173+
// into a map. Returns an error if something fails along the way.
174+
func loadRdeTypeSchemaFromTestResources() (map[string]interface{}, error) {
175+
// Load the RDE type schema
176+
rdeFilePath := "../test-resources/rde_type.json"
177+
_, err := os.Stat(rdeFilePath)
178+
if os.IsNotExist(err) {
179+
return nil, fmt.Errorf("unable to find RDE type file '%s': %s", rdeFilePath, err)
180+
}
181+
182+
rdeFile, err := os.OpenFile(filepath.Clean(rdeFilePath), os.O_RDONLY, 0400)
183+
if err != nil {
184+
return nil, fmt.Errorf("unable to open RDE type file '%s': %s", rdeFilePath, err)
185+
}
186+
defer safeClose(rdeFile)
187+
188+
rdeSchema, err := io.ReadAll(rdeFile)
189+
if err != nil {
190+
return nil, fmt.Errorf("error reading RDE type file %s: %s", rdeFilePath, err)
191+
}
192+
193+
var unmarshaledJson map[string]interface{}
194+
err = json.Unmarshal(rdeSchema, &unmarshaledJson)
195+
if err != nil {
196+
return nil, fmt.Errorf("could not unmarshal RDE type file %s: %s", rdeFilePath, err)
197+
}
198+
199+
return unmarshaledJson, nil
200+
}

0 commit comments

Comments
 (0)