Skip to content

Commit 3741967

Browse files
authored
Add functions for generic OpenAPI CRUD handling (#644)
1 parent babde82 commit 3741967

18 files changed

+1116
-1232
lines changed

.changes/v2.23.0/644-notes.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
* Add internal generic functions to handle CRUD operations for inner and outer entities [GH-644]
2+
* Add section about OpenAPI CRUD functions to `CODING_GUIDELINES.md` [GH-644]
3+
* Convert `DefinedEntityType`, `DefinedEntity`, `DefinedInterface`, `IpSpace`, `IpSpaceUplink`,
4+
`DistributedFirewall`, `DistributedFirewallRule`, `NsxtSegmentProfileTemplate`,
5+
`GetAllIpDiscoveryProfiles`, `GetAllMacDiscoveryProfiles`, `GetAllSpoofGuardProfiles`,
6+
`GetAllQoSProfiles`, `GetAllSegmentSecurityProfiles` to use newly introduced generic CRUD
7+
functions [GH-644]

CODING_GUIDELINES.md

+127
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,134 @@ the request header.
475475

476476
When the tenant context is not needed (system administration calls), we just pass `nil` as `additionalHeader`.
477477

478+
## Generic CRUD functions for OpenAPI entity implementation
478479

480+
Generic CRUD functions are used to minimize boilerplate for entity implementation in the SDK. They
481+
might not always be the way to go when there are very specific operation needs as it is not worth
482+
having a generic function for single use case. In such cases, low level API client function set,
483+
that is located in `openapi.go` can help to perform such operations.
484+
485+
### Terminology
486+
487+
#### inner vs outer types
488+
489+
For the context of generic CRUD function implementation (mainly in files
490+
`govcd/openapi_generic_outer_entities.go`, `govcd/openapi_generic_inner_entities.go`), such terms
491+
are commonly used:
492+
493+
* `inner` type is the type that is responsible for marshaling/unmarshaling API
494+
request payload and is usually inside `types` package. (e.g. `types.IpSpace`,
495+
`types.NsxtAlbPoolMember`, etc.)
496+
* `outer` (type) - this is the type that wraps `inner` type and possibly any other entities that are
497+
required to perform operations for a particular VCD entity. It will almost always include some
498+
reference to client (`VCDClient` or `Client`), which is required to perform API operations. It may
499+
contain additional fields.
500+
501+
Here are the entities mapped in the example below:
502+
503+
* `DistributedFirewall` is the **`outer`** type
504+
* `types.DistributedFirewallRules` is the **`inner`** type (specified in
505+
`DistributedFirewall.DistributedFirewallRuleContainer` field)
506+
* `client` field contains the client that is required for perfoming API operations
507+
* `VdcGroup` field contains additional data (VDC Group reference) that is required for
508+
implementation of this particular entity
509+
510+
```go
511+
type DistributedFirewall struct {
512+
DistributedFirewallRuleContainer *types.DistributedFirewallRules
513+
client *Client
514+
VdcGroup *VdcGroup
515+
}
516+
```
517+
518+
#### crudConfig
519+
520+
A special type `govcd.crudConfig` is used for passing configuration to both - `inner` and `outer`
521+
generic CRUD functions. It also has an internal `validate()` method, which is called upon execution
522+
of any `inner` and `outer` CRUD functions.
523+
524+
See documentation of `govcd.crudConfig` for the options it provides.
525+
526+
### Use cases
527+
528+
The main consideration when to use which functions depends on whether one is dealing with `inner`
529+
types or `outer` types. Both types can be used for quicker development.
530+
531+
Usually, `outer` type is used for a full featured entity (e.g. `IpSpace`, `NsxtEdgeGateway`), while
532+
`inner` suits cases where one needs to perform operations on an already existing or a read-only
533+
entity.
534+
535+
**Hint:** return value of your entity method will always hint whether it is `inner` or `outer` one:
536+
537+
`inner` type function signature example (returns `*types.VdcNetworkProfile`):
538+
539+
```
540+
func (adminVdc *AdminVdc) UpdateVdcNetworkProfile(vdcNetworkProfileConfig *types.VdcNetworkProfile) (*types.VdcNetworkProfile, error) {
541+
```
542+
543+
`outer` type function signature example (returns `*IpSpace`):
544+
545+
```
546+
func (vcdClient *VCDClient) CreateIpSpace(ipSpaceConfig *types.IpSpace) (*IpSpace, error) {
547+
```
548+
549+
#### inner CRUD functions
550+
551+
The entities that match below criteria are usually going to use `inner` crud functions:
552+
* API property manipulation with separate API endpoints for an already existing entity (e.g. VDC
553+
Network Profiles `Vdc.UpdateVdcNetworkProfile`)
554+
* Read only entities (e.g. NSX-T Segment Profiles `VCDClient.GetAllIpDiscoveryProfiles`)
555+
556+
Inner types are more simple as they can be directly used without any additional overhead. There are
557+
7 functions that can be used:
558+
559+
* `createInnerEntity`
560+
* `updateInnerEntity`
561+
* `updateInnerEntityWithHeaders`
562+
* `getInnerEntity`
563+
* `getInnerEntityWithHeaders`
564+
* `deleteEntityById`
565+
* `getAllInnerEntities`
566+
567+
Existing examples of the implementation are:
568+
569+
* `Vdc.GetVdcNetworkProfile`
570+
* `Vdc.UpdateVdcNetworkProfile`
571+
* `Vdc.DeleteVdcNetworkProfile`
572+
* `VCDClient.GetAllIpDiscoveryProfiles`
573+
574+
#### outer CRUD functions
575+
576+
The entities, that implement complete management of a VCD entity will usually rely on `outer` CRUD
577+
functions. Any `outer` type *must* implement `wrap` method (example signature provided below). It is
578+
required to satisfy generic interface constraint (so that generic functions are able to wrap `inner`
579+
type into `outer` type)
580+
581+
```go
582+
func (o OuterEntity) wrap(inner *InnerEntity) *OuterEntity {
583+
o.OuterEntity = inner
584+
return &o
585+
}
586+
```
587+
There are 5 functions for handling CRU(D).
588+
* `createOuterEntity`
589+
* `updateOuterEntity`
590+
* `getOuterEntity`
591+
* `getOuterEntityWithHeaders`
592+
* `getAllOuterEntities`
593+
594+
*Note*: `D` (deletion) in `CRUD` is a simple operation that does not additionally handle data and
595+
`deleteEntityById` is sufficient.
596+
597+
Existing examples of the implementation are:
598+
* `IpSpace`
599+
* `IpSpaceUplink`
600+
* `DistributedFirewall`
601+
* `DistributedFirewallRule`
602+
* `NsxtSegmentProfileTemplate`
603+
* `DefinedEntityType`
604+
* `DefinedInterface`
605+
* `DefinedEntity`
479606

480607
## Testing
481608

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/vmware/go-vcloud-director/v2
22

3-
go 1.19
3+
go 1.21
44

55
require (
66
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195

govcd/adminorg_ldap_test.go

-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package govcd
88

99
import (
1010
"fmt"
11-
"time"
1211

1312
"github.com/vmware/go-vcloud-director/v2/types/v56"
1413
. "gopkg.in/check.v1"
@@ -62,10 +61,6 @@ func (vcd *TestVCD) Test_LDAP(check *C) {
6261
check.Assert(err, IsNil)
6362
}()
6463

65-
sleepTime := 10 * time.Second
66-
fmt.Printf("# Sleeping %s to prevent 'LDAP context not initialized' errors\n", sleepTime.String())
67-
time.Sleep(sleepTime)
68-
6964
// Run tests requiring LDAP from here.
7065
vcd.test_GroupCRUD(check)
7166
vcd.test_GroupFinderGetGenericEntity(check)

0 commit comments

Comments
 (0)