Skip to content

Commit

Permalink
Add code for Solution Landing Zone management [1] (#665)
Browse files Browse the repository at this point in the history
  • Loading branch information
Didainius committed May 17, 2024
1 parent 85e22f8 commit 4913d51
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .changes/v2.24.0/665-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* Added types `SolutionLandingZone` and `types.SolutionLandingZone` for Solution Add-on Landing Zone configuration [GH-665]
* Added method `DefinedEntity.Refresh` to reload RDE state [GH-665]
* Added `VDCClient` methods `CreateSolutionLandingZone`, `GetAllSolutionLandingZones`,
`GetExactlyOneSolutionLandingZone`, `GetSolutionLandingZoneById` for handling Solution Landing Zones [GH-665]
* Added `SolutionLandingZone` methods `Refresh`, `RdeId`, `Update`,
`Delete` to help handling of Solution Landing Zones [GH-665]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ require (

replace (
gopkg.in/check.v1 => github.com/go-check/check v0.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 => github.com/go-yaml/yaml/v2 v2.2.2
gopkg.in/yaml.v2 => github.com/go-yaml/yaml/v2 v2.4.0
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-check/check v0.0.0-20201130134442-10cb98267c6c h1:3LdnoQiW6yLkxRIwSU3pbYp3zqW1daDgoOcOD09OzJs=
github.com/go-check/check v0.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
github.com/go-yaml/yaml/v2 v2.2.2 h1:uw2m9KuKRscWGAkuyoBGQcZSdibhmuXKSJ3+9Tj3zXc=
github.com/go-yaml/yaml/v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
github.com/go-yaml/yaml/v2 v2.4.0 h1:FNqNkD8zxfgoQ6pSknwk+CnijAT6ijXMqcUg7FXN3LU=
github.com/go-yaml/yaml/v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
Expand Down
1 change: 1 addition & 0 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ type TestConfig struct {
VdcGroupEdgeGateway string `yaml:"vdcGroupEdgeGateway"`
NsxtEdgeCluster string `yaml:"nsxtEdgeCluster"`
RoutedNetwork string `yaml:"routedNetwork"`
IsolatedNetwork string `yaml:"isolatedNetwork"`
NsxtAlbControllerUrl string `yaml:"nsxtAlbControllerUrl"`
NsxtAlbControllerUser string `yaml:"nsxtAlbControllerUser"`
NsxtAlbControllerPassword string `yaml:"nsxtAlbControllerPassword"`
Expand Down
16 changes: 15 additions & 1 deletion govcd/defined_entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ package govcd
import (
"encoding/json"
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
"net/url"
"strings"

"github.com/vmware/go-vcloud-director/v2/types/v56"
)

const (
Expand Down Expand Up @@ -482,6 +483,19 @@ func (rde *DefinedEntity) Resolve() error {
return nil
}

// Refresh reloads RDE
func (rde *DefinedEntity) Refresh() error {
client := rde.client

refreshedRde, err := getRdeById(client, rde.DefinedEntity.ID)
if err != nil {
return fmt.Errorf("error refreshing RDE: %s", err)
}
rde.DefinedEntity = refreshedRde.DefinedEntity

return nil
}

// Update updates the receiver Runtime Defined Entity with the values given by the input. This method is useful
// if rde.Resolve() failed and a JSON entity change is needed.
// Updating a RDE populates the ETag field in the receiver object.
Expand Down
32 changes: 32 additions & 0 deletions govcd/generic_functions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package govcd

import (
"encoding/json"
"fmt"
"reflect"
)
Expand Down Expand Up @@ -96,3 +97,34 @@ func localFilterOneOrError[E any](entityLabel string, entities []*E, fieldName,

return oneOrError(fieldName, expectedFieldValue, filteredValues)
}

// convertAnyToRdeEntity unmarshals any entity to map[string]interface{}
func convertAnyToRdeEntity[E any](entityCfg *E) (map[string]interface{}, error) {
jsonText, err := json.Marshal(entityCfg)
if err != nil {
return nil, fmt.Errorf("error marshalling configuration :%s", err)
}

var unmarshalledRdeEntityJson map[string]interface{}
err = json.Unmarshal(jsonText, &unmarshalledRdeEntityJson)
if err != nil {
return nil, fmt.Errorf("error unmarshalling configuration :%s", err)
}

return unmarshalledRdeEntityJson, nil
}

func convertRdeEntityToAny[E any](content map[string]interface{}) (*E, error) {
jsonText2, err := json.Marshal(content)
if err != nil {
return nil, fmt.Errorf("error converting entity to type: %s", err)
}

result := new(E)
err = json.Unmarshal(jsonText2, result)
if err != nil {
return nil, fmt.Errorf("error converting entity to type: %s", err)
}

return result, nil
}
214 changes: 214 additions & 0 deletions govcd/landing_zone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright 2024 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"net/url"
"strings"

"github.com/vmware/go-vcloud-director/v2/types/v56"
)

// slzRdeType sets Runtime Defined Entity Type to be used across multiple calls
var slzRdeType = [3]string{"vmware", "solutions_organization", "1.0.0"}

// SolutionLandingZone controls VCD Solution Add-On Landing Zone. It does so by wrapping RDE for
// entity types vmware:solutions_organization:1.0.0.
//
// Up to VCD 10.5.1.1 ,there can only be one single RDE instance for landing zone.
type SolutionLandingZone struct {
// SolutionLandingZoneType defines internal content of RDE (`types.DefinedEntity.State`)
SolutionLandingZoneType *types.SolutionLandingZoneType
// DefinedEntity contains parent defined entity that contains SolutionLandingZoneType in
// "Entity" field
DefinedEntity *DefinedEntity
vcdClient *VCDClient
}

// CreateSolutionLandingZone configures VCD Solution Add-On Landing Zone. It does so by performing
// the following steps:
//
// 1. Creates Solution Landing Zone RDE based on type urn:vcloud:type:vmware:solutions_organization:1.0.0
// 2. Resolves the RDE
func (vcdClient *VCDClient) CreateSolutionLandingZone(slzCfg *types.SolutionLandingZoneType) (*SolutionLandingZone, error) {
// 1. Check that RDE type exists
rdeType, err := vcdClient.GetRdeType(slzRdeType[0], slzRdeType[1], slzRdeType[2])
if err != nil {
return nil, fmt.Errorf("error retrieving RDE Type for Solution Landing zone: %s", err)
}

// 2. Convert more precise structure to fit DefinedEntity.DefinedEntity.Entity
unmarshalledRdeEntityJson, err := convertAnyToRdeEntity(slzCfg)
if err != nil {
return nil, err
}

// 3. Construct payload
entityCfg := &types.DefinedEntity{
EntityType: "urn:vcloud:type:" + strings.Join(slzRdeType[:], ":"),
Name: "Solutions Organization",
State: addrOf("PRE_CREATED"),
// Processed solution landing zone
Entity: unmarshalledRdeEntityJson,
}

// 4. Create RDE
createdRdeEntity, err := rdeType.CreateRde(*entityCfg, nil)
if err != nil {
return nil, fmt.Errorf("error creating RDE entity: %s", err)
}

// 5. Resolve RDE
err = createdRdeEntity.Resolve()
if err != nil {
return nil, fmt.Errorf("error resolving Solutions add-on after creating: %s", err)
}

// 6. Reload RDE
err = createdRdeEntity.Refresh()
if err != nil {
return nil, fmt.Errorf("error refreshing RDE after resolving: %s", err)
}

result, err := convertRdeEntityToAny[types.SolutionLandingZoneType](createdRdeEntity.DefinedEntity.Entity)
if err != nil {
return nil, err
}

returnType := SolutionLandingZone{
SolutionLandingZoneType: result,
vcdClient: vcdClient,
DefinedEntity: createdRdeEntity,
}

return &returnType, nil
}

// GetAllSolutionLandingZones retrieves all Solution Landing Zones
//
// Note: Up to VCD 10.5.1.1 there can be only a single RDE entry (one SLZ per VCD)
func (vcdClient *VCDClient) GetAllSolutionLandingZones(queryParameters url.Values) ([]*SolutionLandingZone, error) {
allSlzs, err := vcdClient.GetAllRdes(slzRdeType[0], slzRdeType[1], slzRdeType[2], queryParameters)
if err != nil {
return nil, fmt.Errorf("error retrieving all SLZs: %s", err)
}

results := make([]*SolutionLandingZone, len(allSlzs))
for slzRdeIndex, slzRde := range allSlzs {

slz, err := convertRdeEntityToAny[types.SolutionLandingZoneType](slzRde.DefinedEntity.Entity)
if err != nil {
return nil, fmt.Errorf("error converting RDE to SLZ: %s", err)
}

results[slzRdeIndex] = &SolutionLandingZone{
vcdClient: vcdClient,
DefinedEntity: slzRde,
SolutionLandingZoneType: slz,
}
}

return results, nil
}

// GetExactlyOneSolutionLandingZone will get single Solution Landing Zone RDE or fail.
// There can be only one Solution Landing Zone in VCD, but because it is backed by RDE - it can
// occur that due to some error there is more than one RDE Entity
func (vcdClient *VCDClient) GetExactlyOneSolutionLandingZone() (*SolutionLandingZone, error) {
allSlzs, err := vcdClient.GetAllSolutionLandingZones(nil)
if err != nil {
return nil, fmt.Errorf("error retrieving all Solution Landing Zones: %s", err)
}

return oneOrError("rde", strings.Join(slzRdeType[:], ":"), allSlzs)
}

// GetSolutionLandingZoneById retrieves Solution Landing Zone by ID
//
// Note: defined entity ID must be used that can be accessed either by `SolutionLandingZone.Id()`
// method or directly in `SolutionLandingZone.DefinedEntity.DefinedEntity.ID` field
func (vcdClient *VCDClient) GetSolutionLandingZoneById(id string) (*SolutionLandingZone, error) {
if id == "" {
return nil, fmt.Errorf("id must be specified")
}
rde, err := getRdeById(&vcdClient.Client, id)
if err != nil {
return nil, fmt.Errorf("error retrieving RDE by ID: %s", err)
}

result, err := convertRdeEntityToAny[types.SolutionLandingZoneType](rde.DefinedEntity.Entity)
if err != nil {
return nil, err
}

packages := &SolutionLandingZone{
SolutionLandingZoneType: result,
vcdClient: vcdClient,
DefinedEntity: rde,
}

return packages, nil
}

// Refresh reloads parent RDE data
func (slz *SolutionLandingZone) Refresh() error {
err := slz.DefinedEntity.Refresh()
if err != nil {
return err
}

// Repackage created RDE "Entity" to more exact type
result, err := convertRdeEntityToAny[types.SolutionLandingZoneType](slz.DefinedEntity.DefinedEntity.Entity)
if err != nil {
return err
}

slz.SolutionLandingZoneType = result

return nil
}

// RdeId is a shorthand to retrieve ID of parent runtime defined entity
func (slz *SolutionLandingZone) RdeId() string {
if slz == nil || slz.DefinedEntity == nil || slz.DefinedEntity.DefinedEntity == nil {
return ""
}

return slz.DefinedEntity.DefinedEntity.ID
}

// Update Solution Landing Zone
func (slz *SolutionLandingZone) Update(slzCfg *types.SolutionLandingZoneType) (*SolutionLandingZone, error) {
unmarshalledRdeEntityJson, err := convertAnyToRdeEntity(slzCfg)
if err != nil {
return nil, err
}

slz.DefinedEntity.DefinedEntity.Entity = unmarshalledRdeEntityJson

err = slz.DefinedEntity.Update(*slz.DefinedEntity.DefinedEntity)
if err != nil {
return nil, err
}

result, err := convertRdeEntityToAny[types.SolutionLandingZoneType](slz.DefinedEntity.DefinedEntity.Entity)
if err != nil {
return nil, err
}

packages := SolutionLandingZone{
SolutionLandingZoneType: result,
vcdClient: slz.vcdClient,
DefinedEntity: slz.DefinedEntity,
}

return &packages, nil
}

// Delete removes the RDE that defines Solution Landing Zone
func (slz *SolutionLandingZone) Delete() error {
return slz.DefinedEntity.Delete()
}

0 comments on commit 4913d51

Please sign in to comment.