github.com/vmware/go-vcloud-director/v2@v2.24.0/CODING_GUIDELINES.md (about)

     1  # Coding guidelines
     2  
     3  
     4  ## Principles
     5  
     6  The functions, entities, and methods in this library have the wide goal of providing access to vCD functionality
     7  using Go clients.
     8  A more focused goal is to support the [Terraform Provider for vCD](https://github.com/terraform-providers/terraform-provider-vcd).
     9  When in doubt about the direction of development, we should facilitate the path towards making the code usable and maintainable
    10  in the above project.
    11  
    12  
    13  ## Create new entities
    14  
    15  A new entity must have its type defined in `types/56/types.go`. If the type is not already there, it should be 
    16  added using the [vCD API](https://code.vmware.com/apis/72/vcloud-director), and possibly reusing components already defined
    17  in `types.go`.
    18  
    19  The new entity should have a structure in `entity.go` as
    20  
    21  ```go
    22  type Entity struct {
    23  	Entity *types.Entity
    24  	client *VCDClient
    25  	// Optional, in some cases: Parent *Parent
    26  }
    27  ```
    28  
    29  The entity should have at least the following:
    30  
    31  ```
    32  (parent *Parent) CreateEntityAsync(input *types.Entity) (Task, error)
    33  (parent *Parent) CreateEntity(input *types.Entity) (*Entity, error)
    34  ```
    35  
    36  The second form will invoke the `*Async` method, run task.WaitCompletion(), and then retrieving the new entity
    37  from the parent and returning it.
    38  
    39  If the API does not provide a task, the second method will be sufficient.
    40  
    41  If the structure is exceedingly complex, we can use two approaches:
    42  
    43  1. if the parameters needed to create the entity are less than 4, we can pass them as argument
    44  
    45  ```go
    46  (parent *Parent) CreateEntityAsync(field1, field2 string, field3 bool) (Task, error)
    47  ```
    48  
    49  2. If there are too many parameters to pass, we can create a simplified structure:
    50  
    51  ```go
    52  type EntityInput struct {
    53  	field1 string
    54  	field2 string
    55  	field3 bool
    56  	field4 bool
    57  	field5 int
    58  	field6 string
    59  	field7 []string
    60  }
    61  
    62  (parent *Parent) CreateEntityAsync(simple EntityInput) (Task, error)
    63  ```
    64  
    65  The latter approach should be preferred when the simplified structure would be a one-to-one match with the corresponding
    66  resource in Terraform.
    67  
    68  ## Calling the API
    69  
    70  Calls to the vCD API should not be sent directly, but using one of the following functions from `api.go:
    71  
    72  ```go
    73  // Helper function creates request, runs it, check responses and parses out interface from response.
    74  // pathURL - request URL
    75  // requestType - HTTP method type
    76  // contentType - value to set for "Content-Type"
    77  // errorMessage - error message to return when error happens
    78  // payload - XML struct which will be marshalled and added as body/payload
    79  // out - structure to be used for unmarshalling xml
    80  // E.g. 	unmarshalledAdminOrg := &types.AdminOrg{}
    81  // client.ExecuteRequest(adminOrg.AdminOrg.HREF, http.MethodGet, "", "error refreshing organization: %s", nil, unmarshalledAdminOrg)
    82  func (client *Client) ExecuteRequest(pathURL, requestType, contentType, errorMessage string, payload, out interface{}) (*http.Response, error)
    83  ```
    84  
    85  ```go
    86  // Helper function creates request, runs it, checks response and parses task from response.
    87  // pathURL - request URL
    88  // requestType - HTTP method type
    89  // contentType - value to set for "Content-Type"
    90  // errorMessage - error message to return when error happens
    91  // payload - XML struct which will be marshalled and added as body/payload
    92  // E.g. client.ExecuteTaskRequest(updateDiskLink.HREF, http.MethodPut, updateDiskLink.Type, "error updating disk: %s", xmlPayload)
    93  func (client *Client) ExecuteTaskRequest(pathURL, requestType, contentType, errorMessage string, payload interface{}) (Task, error) 
    94  ```
    95  
    96  ```go
    97  // Helper function creates request, runs it, checks response and do not expect any values from it.
    98  // pathURL - request URL
    99  // requestType - HTTP method type
   100  // contentType - value to set for "Content-Type"
   101  // errorMessage - error message to return when error happens
   102  // payload - XML struct which will be marshalled and added as body/payload
   103  // E.g. client.ExecuteRequestWithoutResponse(catalogItemHREF.String(), http.MethodDelete, "", "error deleting Catalog item: %s", nil)
   104  func (client *Client) ExecuteRequestWithoutResponse(pathURL, requestType, contentType, errorMessage string, payload interface{}) error 
   105  ```
   106  
   107  ```go
   108  // ExecuteRequestWithCustomError sends the request and checks for 2xx response. If the returned status code
   109  // was not as expected - the returned error will be unmarshaled to `errType` which implements Go's standard `error`
   110  // interface.
   111  func (client *Client) ExecuteRequestWithCustomError(pathURL, requestType, contentType, errorMessage string,
   112  	payload interface{}, errType error) (*http.Response, error) 
   113  ```
   114  
   115  In addition to saving code and time by reducing the boilerplate, these functions also trigger debugging calls that make the code 
   116  easier to monitor.
   117  Using any of the above calls will result in the standard log i
   118  (See [LOGGING.md](https://github.com/vmware/go-vcloud-director/blob/main/util/LOGGING.md)) recording all the requests and responses
   119  on demand, and also triggering debug output for specific calls (see `enableDebugShowRequest` and `enableDebugShowResponse`
   120  and the corresponding `disable*` in `api.go`).
   121  
   122  
   123  ## Implementing search methods
   124  
   125  Each entity should have the following methods:
   126  
   127  ```
   128  // OPTIONAL
   129  (parent *Parent) GetEntityByHref(href string) (*Entity, error)
   130  
   131  // ALWAYS
   132  (parent *Parent) GetEntityByName(name string) (*Entity, error)
   133  (parent *Parent) GetEntityById(id string) (*Entity, error)
   134  (parent *Parent) GetEntityByNameOrId(identifier string) (*Entity, error)
   135  ```
   136  
   137  For example, the parent for `Vdc` is `Org`, the parent for `EdgeGateway` is `Vdc`.
   138  If the entity is at the top level (such as `Org`, `ExternalNetwork`), the parent is `VCDClient`.
   139  
   140  These methods return a pointer to the entity's structure and a nil error when the search was successful,
   141  a nil pointer and an error in every other case.
   142  When the method can establish that the entity was not found because it did not appear in the
   143  parent's list of entities, the method will return `ErrorEntityNotFound`.
   144  In no cases we return a nil error when the method fails to find the entity.
   145  The "ALWAYS" methods can optionally add a Boolean `refresh` argument, signifying that the parent should be refreshed
   146  prior to attempting a search.
   147  
   148  Note: We are in the process of replacing methods that don't adhere to the above principles (for example, return a
   149  structure instead of a pointer, return a nil error on not-found, etc).
   150  
   151  ## Implementing functions to support different vCD API versions
   152  
   153  Functions dealing with different versions should use a matrix structure to identify which calls to run according to the 
   154  highest API version supported by vCD. An example can be found in adminvdc.go.
   155  
   156  Note: use this pattern for adding new vCD functionality, which is not available in the earliest API version supported 
   157  by the code base (as indicated by `Client.APIVersion`).
   158  
   159  ```
   160  type vdcVersionedFunc struct {
   161  	SupportedVersion string
   162  	CreateVdc        func(adminOrg *AdminOrg, vdcConfiguration *types.VdcConfiguration) (*Vdc, error)
   163  	CreateVdcAsync   func(adminOrg *AdminOrg, vdcConfiguration *types.VdcConfiguration) (Task, error)
   164  	UpdateVdc        func(adminVdc *AdminVdc) (*AdminVdc, error)
   165  	UpdateVdcAsync   func(adminVdc *AdminVdc) (Task, error)
   166  }
   167  
   168  var vdcVersionedFuncsV95 = vdcVersionedFuncs{
   169  	SupportedVersion: "31.0",
   170  	CreateVdc:        createVdc,
   171  	CreateVdcAsync:   createVdcAsync,
   172  	UpdateVdc:        updateVdc,
   173  	UpdateVdcAsync:   updateVdcAsync,
   174  }
   175  
   176  var vdcVersionedFuncsV97 = vdcVersionedFuncs{
   177  	SupportedVersion: "32.0",
   178  	CreateVdc:        createVdcV97,
   179  	CreateVdcAsync:   createVdcAsyncV97,
   180  	UpdateVdc:        updateVdcV97,
   181  	UpdateVdcAsync:   updateVdcAsyncV97,
   182  }
   183  
   184  var vdcVersionedFuncsByVcdVersion = map[string]vdcVersionedFuncs{
   185  	"vdc9.5":  vdcVersionedFuncsV95,
   186  	"vdc9.7":  vdcVersionedFuncsV97,
   187  	"vdc10.0": vdcVersionedFuncsV97
   188  }
   189  
   190  func (adminOrg *AdminOrg) CreateOrgVdc(vdcConfiguration *types.VdcConfiguration) (*Vdc, error) {
   191  	apiVersion, err := adminOrg.client.MaxSupportedVersion()
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	vdcFunctions, ok := vdcVersionedFuncsByVcdVersion["vdc"+apiVersionToVcdVersion[apiVersion]]
   196  	if !ok {
   197  		return nil, fmt.Errorf("no entity type found %s", "vdc"+apiVersion)
   198  	}
   199  	if vdcFunctions.CreateVdc == nil {
   200  		return nil, fmt.Errorf("function CreateVdc is not defined for %s", "vdc"+apiVersion)
   201  	}
   202      util.Logger.Printf("[DEBUG] CreateOrgVdc call function for version %s", vdcFunctions.SupportedVersion)
   203  	return vdcFunctions.CreateVdc(adminOrg, vdcConfiguration)
   204  }
   205  ```
   206  
   207   ## Query engine
   208   
   209  The query engine is a search engine that is based on queries (see `query.go`) with additional filters.
   210  
   211  The query runs through the function `client.SearchByFilter` (`filter_engine.go`), which requires a `queryType` (string),
   212  and a set of criteria (`*FilterDef`).
   213  
   214  We can search by one of the types handled by `queryFieldsOnDemand` (`query_metadata.go`), such as 
   215  
   216  ```go
   217  const (
   218  	QtVappTemplate      = "vappTemplate"      // vApp template
   219  	QtAdminVappTemplate = "adminVAppTemplate" // vApp template as admin
   220  	QtEdgeGateway       = "edgeGateway"       // edge gateway
   221  	QtOrgVdcNetwork     = "orgVdcNetwork"     // Org VDC network
   222  	QtAdminCatalog      = "adminCatalog"      // catalog
   223  	QtCatalogItem       = "catalogItem"       // catalog item
   224  	QtAdminCatalogItem  = "adminCatalogItem"  // catalog item as admin
   225  	QtAdminMedia        = "adminMedia"        // media item as admin
   226  	QtMedia             = "media"             // media item
   227  )
   228  ```
   229  There are two reasons for this limitation:
   230  
   231  * If we want to include metadata, we need to add the metadata fields to the list of fields we want the query to fetch.
   232  * Unfortunately, not all fields defined in the corresponding type is accepted by the `fields` parameter in a query.
   233  The fields returned by `queryFieldsOnDemand` are the one that have been proven to be accepted.
   234  
   235  
   236  The `FilterDef` type is defined as follows (`filter_utils.go`)
   237  ```go
   238  type FilterDef struct {
   239  	// A collection of filters (with keys from SupportedFilters)
   240  	Filters map[string]string
   241  
   242  	// A list of metadata filters
   243  	Metadata []MetadataDef
   244  
   245  	// If true, the query will include metadata fields and search for exact values.
   246  	// Otherwise, the engine will collect metadata fields and search by regexp
   247  	UseMetadataApiFilter bool
   248  }
   249  ```
   250  
   251  A `FilterDef` may contain several filters, such as:
   252  
   253  ```go
   254  criteria := &govcd.FilterDef{
   255      Filters:  {
   256          "name":   "^Centos",
   257          "date":   "> 2020-02-02",
   258          "latest": "true",
   259      },
   260      Metadata: {
   261          {
   262              Key:      "dept",
   263              Type:     "STRING",
   264              Value:    "ST\\w+",
   265              IsSystem: false,
   266          },
   267      },
   268      UseMetadataApiFilter: false,
   269  }
   270  ```
   271  
   272  The set of criteria above will find an item with name starting with "Centos", created after February 2nd, 2020, with
   273  a metadata key "dept" associated with a value starting with "ST". If more than one item is found, the engine will return
   274  the newest one (because of `"latest": "true"`)
   275  The argument `UseMetadataApiFilter`, when true, instructs the engine to run the search with metadata values. Meaning that
   276  the query will contain a clause `filter=metadata:KeyName==TYPE:Value`. If `IsSystem` is true, the clause will become
   277  `filter=metadata@SYSTEM:KeyName==TYPE:Value`. This search can't evaluate regular expressions, because it goes directly 
   278  to vCD.
   279  
   280  An example of `SYSTEM` metadata values is the set of annotations that the vCD adds to a vApp template when we save a
   281  vApp to a catalog.
   282  
   283  ```
   284    "metadata" = {
   285      "vapp.origin.id" = "deadbeef-2913-4ed7-b943-79a91620fd52" // vApp ID
   286      "vapp.origin.name" = "my_vapp_name"
   287      "vapp.origin.type" = "com.vmware.vcloud.entity.vapp"
   288    }
   289  ```
   290  
   291  The engine returns a list of `QueryItem`, and interface that defines several methods used to help evaluate the search
   292  conditions.
   293  
   294  ### How to use the query engine
   295  
   296  Here is an example of how to retrieve a media item.
   297  The criteria ask for the newest item created after the 2nd of February 2020, containing a metadata field named "abc",
   298  with a non-empty value.
   299  
   300  ```go
   301              criteria := &govcd.FilterDef{
   302                  Filters:  map[string]string{
   303                      "date":"> 2020-02-02", 
   304                      "latest": "true",
   305                   },
   306                  Metadata: []govcd.MetadataDef{
   307                      {
   308                          Key:      "abc",
   309                          Type:     "STRING",
   310                          Value:    "\\S+",
   311                          IsSystem: false,
   312                      },
   313                  },
   314                  UseMetadataApiFilter: false,
   315              }
   316  			queryType := govcd.QtMedia
   317  			if vcdClient.Client.IsSysAdmin {
   318  				queryType = govcd.QtAdminMedia
   319  			}
   320  			queryItems, explanation, err := vcdClient.Client.SearchByFilter(queryType, criteria)
   321  			if err != nil {
   322  				return err
   323  			}
   324  			if len(queryItems) == 0 {
   325  				return fmt.Errorf("no media found with given criteria (%s)", explanation)
   326  			}
   327  			if len(queryItems) > 1 {
   328                  // deal with several items
   329  				var itemNames = make([]string, len(queryItems))
   330  				for i, item := range queryItems {
   331  					itemNames[i] = item.GetName()
   332  				}
   333  				return fmt.Errorf("more than one media item found by given criteria: %v", itemNames)
   334  			}
   335              // retrieve the full entity for the item found
   336  			media, err = catalog.GetMediaByHref(queryItems[0].GetHref())
   337  ```
   338  
   339  The `explanation` returned by `SearchByFilter` contains the details of the criteria as they were understood by the
   340  engine, and the detail of how each comparison with other items was evaluated. This is useful to create meaningful error
   341  messages.
   342  
   343  ### Supporting a new type in the query engine
   344  
   345  To add a type to the search engine, we need the following:
   346  
   347  1. Add the type to `types.QueryResultRecordsType` (`types.go`), or, if the type exists, make sure it includes `Metadata`
   348  2. Add the list of supported fields to `queryFieldsOnDemand` (`query_metadata.go`)
   349  3. Implement the interface `QueryItem` (`filter_interface.go`), which requires a type localization (such as 
   350  `type QueryMedia  types.MediaRecordType`)
   351  4. Add a clause to `resultToQueryItems` (`filter_interface.go`)
   352  
   353  ## Data inspection checkpoints
   354  
   355  Logs should not be cluttered with excessive detail.
   356  However, sometimes we need to provide such detail when hunting for bugs.
   357  
   358  We can introduce data inspection points, regulated by the environment variable `GOVCD_INSPECT`, which uses a convenient
   359  code to activate the inspection at different points.
   360  
   361  For example, we can mark the inspection points in the query engine with labels "QE1", "QE2", etc., in the network creation
   362  they will be "NET1", "NET2", etc, and then activate them using
   363  `GOVCD_INSPECT=QE2,NET1`.
   364  
   365  In the code, we use the function `dataInspectionRequested(code)` that will check whether the environment variable contains
   366  the  given code.
   367  
   368  ## Tenant Context
   369  
   370  Tenant context is a mechanism in the VCD API to run calls as a tenant when connected as a system administrator.
   371  It is used, for example, in the UI, to start a session as tenant administrator without having credentials for such a user,
   372  or even when there is no such user yet.
   373  The context change works by adding a header to the API call, containing these fields:
   374  
   375  ```
   376  X-Vmware-Vcloud-Tenant-Context: [604cf889-b01e-408b-95ae-67b02a0ecf33]
   377  X-Vmware-Vcloud-Auth-Context:   [org-name]
   378  ```
   379  
   380  The field `X-Vmware-Vcloud-Tenant-Context` contains the bare ID of the organization (it's just the UUID, without the
   381  prefix `urn:vcloud:org:`).
   382  The field `X-Vmware-Vcloud-Auth-Context` contains the organization name.
   383  
   384  ### tenant context: data availability
   385  
   386  From the SDK standpoint, finding the data needed to put together the tenant context is relatively easy when the originator
   387  of the API call is the organization itself (such as `org.GetSomeEntityByName`).
   388  When we deal with objects down the hierarchy, however, things are more difficult. Running a call from a VDC means that
   389  we need to retrieve the parent organization, and extract ID and name. The ID is available through the `Link` structure
   390  of the VDC, but for the name we need to retrieve the organization itself.
   391  
   392  The approach taken in the SDK is to save the tenant context (or a pointer to the parent) in the object that we have just
   393  created. For example, when we create a VDC, we save the organization as a pointer in the `parent` field, and the organization 
   394  itself has a field `TenantContext` with the needed information.
   395  
   396  Here are the types that are needed for tenant context manipulation
   397  ```go
   398  
   399  // tenant_context.go
   400  type TenantContext struct {
   401  	OrgId   string // The bare ID (without prefix) of an organization
   402  	OrgName string // The organization name
   403  }
   404  
   405  // tenant_context.go
   406  type organization interface {
   407  	orgId() string
   408  	orgName() string
   409  	tenantContext() (*TenantContext, error)
   410  	fullObject() interface{}
   411  }
   412  
   413  // org.go
   414  type Org struct {
   415  	Org           *types.Org
   416  	client        *Client
   417  	TenantContext *TenantContext
   418  }
   419  
   420  // adminorg.go
   421  type AdminOrg struct {
   422  	AdminOrg      *types.AdminOrg
   423  	client        *Client
   424  	TenantContext *TenantContext
   425  }
   426  
   427  // vdc.go
   428  type Vdc struct {
   429  	Vdc    *types.Vdc
   430  	client *Client
   431  	parent organization
   432  }
   433  ```
   434  
   435  The `organization` type is an abstraction to include both `Org` and `AdminOrg`. Thus, the VDC object has a pointer to its
   436  parent that is only needed to get the tenant context quickly.
   437  
   438  Each object has a way to get the tenant context by means of a `entity.getTenantContext()`. The information
   439  trickles down from the hierarchy:
   440  
   441  * a VDC gets the tenant context directly from its `parent` field, which has a method `tenantContext()`
   442  * similarly, a Catalog has a `parent` field with the same functionality.
   443  * a vApp will get the tenant context by first retrieving its parent (`vapp.getParentVdc()`) and then asking the parent
   444  for the tenant context.
   445  
   446  ### tenant context: usage
   447  
   448  Once we have the tenant context, we need to pass the information along to the HTTP request that builds the request header,
   449  so that our API call will run in the desired context.
   450  
   451  The basic OpenAPI methods (`Client.OpenApiDeleteItem`, `Client.OpenApiGetAllItems`, `Client.OpenApiGetItem`,
   452  `Client.OpenApiPostItem`, `Client.OpenApiPutItem`, `Client.OpenApiPutItemAsync`, `Client.OpenApiPutItemSync`)  all include
   453  a parameter `additionalHeader map[string]string` containing the information needed to build the tenant context header elements.
   454  
   455  Inside the function where we want to use tenant context, we do these two steps:
   456  
   457  1. retrieve the tenant context
   458  2. add the additional header to the API call.
   459  
   460  For example:
   461  
   462  ```go
   463  func (adminOrg *AdminOrg) GetAllRoles(queryParameters url.Values) ([]*Role, error) {
   464  	tenantContext, err := adminOrg.getTenantContext()
   465  	if err != nil {
   466  		return nil, err
   467  	}
   468  	return getAllRoles(adminOrg.client, queryParameters, getTenantContextHeader(tenantContext))
   469  }
   470  ```
   471  The function `getTenantContextHeader` takes a tenant context and returns a map of strings containing the right header
   472  keys. In the example above, the header is passed to `getAllRoles`, which in turn calls `Client.OpenApiGetAllItems`,
   473  which passes the additional header until it reaches `newOpenApiRequest`, where the tenent context data is inserted in
   474  the request header.
   475  
   476  When the tenant context is not needed (system administration calls), we just pass `nil` as `additionalHeader`.
   477  
   478  ## Generic CRUD functions for OpenAPI entity implementation
   479  
   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`
   606  
   607  ## Testing
   608  
   609  Every feature in the library must include testing. See [TESTING.md](https://github.com/vmware/go-vcloud-director/blob/main/TESTING.md) for more info.