Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Org support #1345

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ vcd/*-out-*.txt

# Go workspace
go.work
go.work.sum

# File cache directory
test-resources/cache/*
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ require (
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.0 // indirect
)

replace github.com/vmware/go-vcloud-director/v2 => github.com/Didainius/go-vcloud-director/v3 v3.0.0-20241017120757-89066b07dd9c
14 changes: 13 additions & 1 deletion vcd/config_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build api || functional || catalog || vapp || network || extnetwork || org || query || vm || vdc || gateway || disk || binary || lb || lbServiceMonitor || lbServerPool || lbAppProfile || lbAppRule || lbVirtualServer || access_control || user || standaloneVm || search || auth || nsxt || role || alb || certificate || vdcGroup || ldap || rde || uiPlugin || providerVdc || cse || slz || multisite || ALL
//go:build api || functional || catalog || vapp || network || extnetwork || org || query || vm || vdc || gateway || disk || binary || lb || lbServiceMonitor || lbServerPool || lbAppProfile || lbAppRule || lbVirtualServer || access_control || user || standaloneVm || search || auth || nsxt || role || alb || certificate || vdcGroup || ldap || rde || uiPlugin || providerVdc || cse || slz || multisite || tm || ALL

package vcd

Expand Down Expand Up @@ -107,6 +107,12 @@ type TestConfig struct {
UseVcdConnectionCache bool `json:"useVcdConnectionCache"`
MaxRetryTimeout int `json:"maxRetryTimeout"`
} `json:"provider"`
Tm struct {
Org string `json:"org"` // temporary field to make skipIfNotTm work
Region string `json:"region"`
RegionStoragePolicy string `json:"regionStoragePolicy"`
Vdc string `json:"vdc"`
} `json:"tm,omitempty"`
VCD struct {
Org string `json:"org"`
Vdc string `json:"vdc"`
Expand Down Expand Up @@ -416,6 +422,12 @@ func skipIfNotSysAdmin(t *testing.T) {
}
}

func skipIfNotTm(t *testing.T) {
if checkVersion(testConfig.Provider.ApiVersion, "< 40.0") {
t.Skip(t.Name() + " requires 'tm'")
}
}

// Gets a list of all variables mentioned in a template
func GetVarsFromTemplate(tmpl string) []string {
var varList []string
Expand Down
101 changes: 101 additions & 0 deletions vcd/datasource_vcd_tm_org.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package vcd

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/vmware/go-vcloud-director/v3/govcd"
"github.com/vmware/go-vcloud-director/v3/types/v56"
)

func datasourceVcdTmOrg() *schema.Resource {
return &schema.Resource{
ReadContext: datasourceVcdTmOrgRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: fmt.Sprintf("The unique identifier in the full URL with which users log in to this %s", labelTmOrg),
},
"display_name": {
Type: schema.TypeString,
Computed: true,
Description: fmt.Sprintf("Appears in the Cloud application as a human-readable name of the %s", labelTmOrg),
},
"description": {
Type: schema.TypeString,
Computed: true,
Description: "Description",
},
"is_enabled": {
Type: schema.TypeBool,
Computed: true,
Description: fmt.Sprintf("Defines if the %s enabled", labelTmOrg),
},
"is_subprovider": { /// Can it be read?
Type: schema.TypeBool,
Computed: true,
Description: fmt.Sprintf("Defines if this can manage other %ss", labelTmOrg),
},
// TODO: TM: validate if all of these computed attributes are effective
"org_vdc_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of VDCs belonging to the %s", labelTmOrg),
},
"catalog_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of catalog belonging to the %s", labelTmOrg),
},
"vapp_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of vApps belonging to the %s", labelTmOrg),
},
"running_vm_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of running VMs in the %s", labelTmOrg),
},
"user_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of users in the %s", labelTmOrg),
},
"disk_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of disks in the %s", labelTmOrg),
},
"can_publish": {
Type: schema.TypeBool,
Computed: true,
Description: fmt.Sprintf("Defines whether the %s can publish catalogs externally", labelTmOrg),
},
"directly_managed_org_count": {
Type: schema.TypeInt,
Computed: true,
Description: fmt.Sprintf("Number of directly managed %ss", labelTmOrg),
},
"is_classic_tenant": {
Type: schema.TypeBool,
Computed: true,
Description: fmt.Sprintf("Defines whether the %s is a classic VRA-style tenant", labelTmOrg),
},
},
}
}

func datasourceVcdTmOrgRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)
c := crudConfig[*govcd.TmOrg, types.TmOrg]{

Check failure on line 95 in vcd/datasource_vcd_tm_org.go

View workflow job for this annotation

GitHub Actions / Check

undefined: govcd.TmOrg

Check failure on line 95 in vcd/datasource_vcd_tm_org.go

View workflow job for this annotation

GitHub Actions / Check

undefined: types.TmOrg
entityLabel: labelTmOrg,
getEntityFunc: vcdClient.GetTmOrgByName,

Check failure on line 97 in vcd/datasource_vcd_tm_org.go

View workflow job for this annotation

GitHub Actions / Check

vcdClient.GetTmOrgByName undefined (type *VCDClient has no field or method GetTmOrgByName)
stateStoreFunc: setTmOrgData,
}
return readDatasource(ctx, d, meta, c)
}
2 changes: 2 additions & 0 deletions vcd/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ var globalDataSourceMap = map[string]*schema.Resource{
"vcd_nsxt_alb_virtual_service_http_req_rules": datasourceVcdAlbVirtualServiceReqRules(), // 3.14
"vcd_nsxt_alb_virtual_service_http_resp_rules": datasourceVcdAlbVirtualServiceRespRules(), // 3.14
"vcd_nsxt_alb_virtual_service_http_sec_rules": datasourceVcdAlbVirtualServiceSecRules(), // 3.14
"vcd_tm_org": datasourceVcdTmOrg(), // 4.0
}

var globalResourceMap = map[string]*schema.Resource{
Expand Down Expand Up @@ -301,6 +302,7 @@ var globalResourceMap = map[string]*schema.Resource{
"vcd_nsxt_alb_virtual_service_http_req_rules": resourceVcdAlbVirtualServiceReqRules(), // 3.14
"vcd_nsxt_alb_virtual_service_http_resp_rules": resourceVcdAlbVirtualServiceRespRules(), // 3.14
"vcd_nsxt_alb_virtual_service_http_sec_rules": resourceVcdAlbVirtualServiceSecRules(), // 3.14
"vcd_tm_org": resourceVcdTmOrg(), // 4.0
}

// Provider returns a terraform.ResourceProvider.
Expand Down
2 changes: 1 addition & 1 deletion vcd/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build api || functional || catalog || vapp || network || extnetwork || org || query || vm || vdc || gateway || disk || binary || lb || lbAppProfile || lbAppRule || lbServiceMonitor || lbServerPool || lbVirtualServer || user || access_control || standaloneVm || search || auth || nsxt || role || alb || certificate || vdcGroup || ldap || rde || uiPlugin || providerVdc || cse || slz || multisite || ALL
//go:build api || functional || catalog || vapp || network || extnetwork || org || query || vm || vdc || gateway || disk || binary || lb || lbAppProfile || lbAppRule || lbServiceMonitor || lbServerPool || lbVirtualServer || user || access_control || standaloneVm || search || auth || nsxt || role || alb || certificate || vdcGroup || ldap || rde || uiPlugin || providerVdc || cse || slz || multisite || tm || ALL

package vcd

Expand Down
214 changes: 214 additions & 0 deletions vcd/resource_generic_crud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package vcd

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/vmware/go-vcloud-director/v3/govcd"
"github.com/vmware/go-vcloud-director/v3/util"
)

// updateDeleter is a type constraint to match only entities that have Update and Delete methods
type updateDeleter[O any, I any] interface {
Update(*I) (O, error)
Delete() error
}

type outerEntityHook[O any] func(O) error
type outerEntityHookInnerEntityType[O, I any] func(O, I) error
type schemaHook func(*VCDClient, *schema.ResourceData) error

// crudConfig
type crudConfig[O updateDeleter[O, I], I any] struct {
// entityLabel to use
entityLabel string

// getTypeFunc is responsible for converting schema fields to inner type
getTypeFunc func(d *schema.ResourceData) (*I, error)
// stateStoreFunc is responsible for storing state
stateStoreFunc func(d *schema.ResourceData, outerType O) error

// createFunc is the function that can create an outer entity based on inner entity config
// (which is created by 'getTypeFunc')
createFunc func(config *I) (O, error)

// resourceReadFunc that will be executed from Create and Update functions
resourceReadFunc func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics

// getEntityFunc is a function that retrieves the entity
// It will use ID for resources and Name for data sources
getEntityFunc func(idOrName string) (O, error)

// preCreateHooks will be executed before the entity is created
preCreateHooks []schemaHook

// preUpdateHooks will be executed before submitting the data for update
preUpdateHooks []outerEntityHookInnerEntityType[O, *I]

// preDeleteHooks will be executed before the entity is deleted
preDeleteHooks []outerEntityHook[O]

// readHooks that will be executed after the entity is read, but before it is stored in state
readHooks []outerEntityHook[O]
}

func createResource[O updateDeleter[O, I], I any](ctx context.Context, d *schema.ResourceData, meta interface{}, c crudConfig[O, I]) diag.Diagnostics {
t, err := c.getTypeFunc(d)
if err != nil {
return diag.Errorf("error getting %s type: %s", c.entityLabel, err)
}

vcdClient := meta.(*VCDClient)
err = execSchemaHook(vcdClient, d, c.preCreateHooks)
if err != nil {
return diag.Errorf("error executing pre-create %s hooks: %s", c.entityLabel, err)
}

createdEntity, err := c.createFunc(t)
if err != nil {
return diag.Errorf("error creating %s: %s", c.entityLabel, err)
}

err = c.stateStoreFunc(d, createdEntity)
if err != nil {
return diag.Errorf("error storing %s to state: %s", c.entityLabel, err)
}

return c.resourceReadFunc(ctx, d, meta)
}

func updateResource[O updateDeleter[O, I], I any](ctx context.Context, d *schema.ResourceData, meta interface{}, c crudConfig[O, I]) diag.Diagnostics {
t, err := c.getTypeFunc(d)
if err != nil {
return diag.Errorf("error getting %s type: %s", c.entityLabel, err)
}

retrievedEntity, err := c.getEntityFunc(d.Id())
if err != nil {
return diag.Errorf("error getting %s: %s", c.entityLabel, err)
}

err = execUpdateEntityHookWithNewInnerType(retrievedEntity, t, c.preUpdateHooks)
if err != nil {
return diag.Errorf("error executing pre-update %s hooks: %s", c.entityLabel, err)
}

_, err = retrievedEntity.Update(t)
if err != nil {
return diag.Errorf("error storing %s to state: %s", c.entityLabel, err)
}

return c.resourceReadFunc(ctx, d, meta)
}

func readResource[O updateDeleter[O, I], I any](_ context.Context, d *schema.ResourceData, _ interface{}, c crudConfig[O, I]) diag.Diagnostics {
retrievedEntity, err := c.getEntityFunc(d.Id())
if err != nil {
if govcd.ContainsNotFound(err) {
util.Logger.Printf("[DEBUG] entity '%s' with ID '%s' not found. Removing from state", c.entityLabel, d.Id())
}
return diag.Errorf("error getting %s: %s", c.entityLabel, err)
}

err = execEntityHook(retrievedEntity, c.readHooks)
if err != nil {
return diag.Errorf("error executing read %s hooks: %s", c.entityLabel, err)
}

err = c.stateStoreFunc(d, retrievedEntity)
if err != nil {
return diag.Errorf("error storing %s to state: %s", c.entityLabel, err)
}

return nil
}

func readDatasource[O updateDeleter[O, I], I any](_ context.Context, d *schema.ResourceData, _ interface{}, c crudConfig[O, I]) diag.Diagnostics {
entityName := d.Get("name").(string)
retrievedEntity, err := c.getEntityFunc(entityName)
if err != nil {
return diag.Errorf("error getting %s by Name '%s': %s", c.entityLabel, entityName, err)
}

err = c.stateStoreFunc(d, retrievedEntity)
if err != nil {
return diag.Errorf("error storing %s to state: %s", c.entityLabel, err)
}

return nil
}

func deleteResource[O updateDeleter[O, I], I any](_ context.Context, d *schema.ResourceData, _ interface{}, c crudConfig[O, I]) diag.Diagnostics {
retrievedEntity, err := c.getEntityFunc(d.Id())
if err != nil {
return diag.Errorf("error getting %s: %s", c.entityLabel, err)
}

err = execEntityHook(retrievedEntity, c.preDeleteHooks)
if err != nil {
return diag.Errorf("error executing pre-delete %s hooks: %s", c.entityLabel, err)
}

err = retrievedEntity.Delete()
if err != nil {
return diag.Errorf("error storing %s to state: %s", c.entityLabel, err)
}

return nil
}

func execSchemaHook(vcdClient *VCDClient, d *schema.ResourceData, runList []schemaHook) error {
if len(runList) == 0 {
util.Logger.Printf("[DEBUG] No hooks to execute")
return nil
}

var err error
for i := range runList {
err = runList[i](vcdClient, d)
if err != nil {
return fmt.Errorf("error executing hook: %s", err)
}

}

return nil
}

func execEntityHook[O any](outerEntity O, runList []outerEntityHook[O]) error {
if len(runList) == 0 {
util.Logger.Printf("[DEBUG] No hooks to execute")
return nil
}

var err error
for i := range runList {
err = runList[i](outerEntity)
if err != nil {
return fmt.Errorf("error executing hook: %s", err)
}

}

return nil
}

func execUpdateEntityHookWithNewInnerType[O, I any](outerEntity O, newInnerEntity I, runList []outerEntityHookInnerEntityType[O, I]) error {
if len(runList) == 0 {
util.Logger.Printf("[DEBUG] No hooks to execute")
return nil
}

var err error
for i := range runList {
err = runList[i](outerEntity, newInnerEntity)
if err != nil {
return fmt.Errorf("error executing hook: %s", err)
}

}

return nil
}
Loading
Loading