Skip to content
2 changes: 2 additions & 0 deletions .changes/v1.0.0/19-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* **New Resource:** `vcfa_role` to manage Roles [GH-19]
* **New Data Source:** `vcfa_role` to read existing Roles [GH-19]
52 changes: 52 additions & 0 deletions vcfa/datasource_vcfa_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package vcfa

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func datasourceVcfaRole() *schema.Resource {
return &schema.Resource{
ReadContext: datasourceVcfaRoleRead,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: fmt.Sprintf("Name of the %s", labelVcfaRole),
},
"org_id": {
Type: schema.TypeString,
Required: true,
Description: fmt.Sprintf("The ID of the %s of the %s", labelVcfaOrg, labelVcfaRole),
},
"description": {
Type: schema.TypeString,
Computed: true,
Description: fmt.Sprintf("%s description", labelVcfaRole),
},
"bundle_key": {
Type: schema.TypeString,
Computed: true,
Description: "Key used for internationalization",
},
"read_only": {
Type: schema.TypeBool,
Computed: true,
Description: fmt.Sprintf("Whether this %s is read-only", labelVcfaRole),
},
"rights": {
Type: schema.TypeSet,
Computed: true,
Description: fmt.Sprintf("Set of %ss assigned to this %s", labelVcfaRight, labelVcfaRole),
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}

func datasourceVcfaRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return genericVcfaRoleRead(ctx, d, meta, "datasource", "read")
}
2 changes: 2 additions & 0 deletions vcfa/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var globalDataSourceMap = map[string]*schema.Resource{
"vcfa_org_oidc": datasourceVcfaOrgOidc(), // 1.0
"vcfa_right": datasourceVcfaRight(), // 1.0
"vcfa_rights_bundle": datasourceVcfaRightsBundle(), // 1.0
"vcfa_role": datasourceVcfaRole(), // 1.0
}

var globalResourceMap = map[string]*schema.Resource{
Expand All @@ -72,6 +73,7 @@ var globalResourceMap = map[string]*schema.Resource{
"vcfa_org_regional_networking": resourceVcfaOrgRegionalNetworking(), // 1.0
"vcfa_org_oidc": resourceVcfaOrgOidc(), // 1.0
"vcfa_rights_bundle": resourceVcfaRightsBundle(), // 1.0
"vcfa_role": resourceVcfaRole(), // 1.0
}

// Provider returns a terraform.ResourceProvider.
Expand Down
18 changes: 13 additions & 5 deletions vcfa/resource_vcfa_rights_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func resourceVcfaRightsBundleCreate(ctx context.Context, d *schema.ResourceData,
rightsBundleName := d.Get("name").(string)
publishToAllTenants := d.Get("publish_to_all_orgs").(bool)

inputRights, err := getRights(vcdClient, fmt.Sprintf("%s create", labelVcfaRightsBundle), d)
inputRights, err := getRights(vcdClient, nil, fmt.Sprintf("%s create", labelVcfaRightsBundle), d)
if err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -196,7 +196,7 @@ func resourceVcfaRightsBundleUpdate(ctx context.Context, d *schema.ResourceData,
var changedRights = d.HasChange("rights")
var changedTenants = d.HasChange("org_ids") || d.HasChange("publish_to_all_orgs")
if changedRights {
inputRights, err = getRights(vcdClient, fmt.Sprintf("%s update", labelVcfaRightsBundle), d)
inputRights, err = getRights(vcdClient, nil, fmt.Sprintf("%s update", labelVcfaRightsBundle), d)
if err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -330,19 +330,27 @@ func getOrganizations(client *VCDClient, label string, d *schema.ResourceData) (
// getRights will collect the list of rights of a rights collection (role, global role, rights bundle)
// and check whether the necessary implied rights are included.
// The "label" identifies the calling resource and operation and it is used to form error messages
func getRights(client *VCDClient, label string, d *schema.ResourceData) ([]types.OpenApiReference, error) {
// TODO: TM: Switch to TmOrg type and implement GetRightByName
func getRights(client *VCDClient, org *govcd.AdminOrg, label string, d *schema.ResourceData) ([]types.OpenApiReference, error) {
var inputRights []types.OpenApiReference

if client == nil {
return nil, fmt.Errorf("[getRights - %s] client was empty", label)
}
rights := d.Get("rights").(*schema.Set).List()

var right *types.Right
var err error

for _, r := range rights {
rn := r.(string)
right, err := client.Client.GetRightByName(rn)
if org != nil {
right, err = org.GetRightByName(rn)
} else {
right, err = client.Client.GetRightByName(rn)
}
if err != nil {
return nil, fmt.Errorf("[%s] error retrieving %s %s: %s", label, labelVcfaRight, rn, err)
return nil, fmt.Errorf("[%s] error retrieving %s '%s': %s", label, labelVcfaRight, rn, err)
}
inputRights = append(inputRights, types.OpenApiReference{Name: rn, ID: right.ID})
}
Expand Down
260 changes: 260 additions & 0 deletions vcfa/resource_vcfa_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package vcfa

import (
"context"
"fmt"
"strings"

"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"
)

const labelVcfaRole = "Role"

func resourceVcfaRole() *schema.Resource {
return &schema.Resource{
CreateContext: resourceVcfaRoleCreate,
ReadContext: resourceVcfaRoleRead,
UpdateContext: resourceVcfaRoleUpdate,
DeleteContext: resourceVcfaRoleDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceVcfaRoleImport,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: fmt.Sprintf("Name of the %s", labelVcfaRole),
},
"org_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: fmt.Sprintf("The ID of the %s of the %s", labelVcfaOrg, labelVcfaRole),
},
"description": {
Type: schema.TypeString,
Required: true,
Description: fmt.Sprintf("%s description", labelVcfaRole),
},
"bundle_key": {
Type: schema.TypeString,
Computed: true,
Description: "Key used for internationalization",
},
"read_only": {
Type: schema.TypeBool,
Computed: true,
Description: fmt.Sprintf("Whether this %s is read-only", labelVcfaRole),
},
"rights": {
Type: schema.TypeSet,
Optional: true,
Description: fmt.Sprintf("Set of %ss assigned to this %s", labelVcfaRight, labelVcfaRole),
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}

func resourceVcfaRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

roleName := d.Get("name").(string)
orgId := d.Get("org_id").(string)

// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
org, err := vcdClient.GetAdminOrgById(orgId)
if err != nil {
return diag.Errorf("[%s create] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgId, err)
}

// Check rights early, so that we can show a friendly error message when there are missing implied rights
inputRights, err := getRights(vcdClient, org, fmt.Sprintf("%s create", labelVcfaRole), d)
if err != nil {
return diag.FromErr(err)
}

role, err := org.CreateRole(&types.Role{
Name: roleName,
Description: d.Get("description").(string),
BundleKey: types.VcloudUndefinedKey,
})
if err != nil {
return diag.Errorf("[%s create] error creating %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
}
if len(inputRights) > 0 {
err = role.AddRights(inputRights)
if err != nil {
return diag.Errorf("[%s create] error adding %ss to %s '%s': %s", labelVcfaRole, labelVcfaRight, labelVcfaRole, roleName, err)
}
}

d.SetId(role.Role.ID)
return genericVcfaRoleRead(ctx, d, meta, "resource", "create")
}

func resourceVcfaRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return genericVcfaRoleRead(ctx, d, meta, "resource", "read")
}

func genericVcfaRoleRead(_ context.Context, d *schema.ResourceData, meta interface{}, origin, operation string) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

roleName := d.Get("name").(string)
orgId := d.Get("org_id").(string)
identifier := d.Id()

var role *govcd.Role
var err error

// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
org, err := vcdClient.GetAdminOrgById(orgId)
if err != nil {
return diag.Errorf("[%s %s-%s] error retrieving %s '%s': %s", labelVcfaRole, operation, origin, labelVcfaOrg, orgId, err)
}

if identifier == "" {
role, err = org.GetRoleByName(roleName)
} else {
role, err = org.GetRoleById(identifier)
}
if err != nil {
if origin == "resource" && govcd.ContainsNotFound(err) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}

d.SetId(role.Role.ID)
dSet(d, "description", role.Role.Description)
dSet(d, "bundle_key", role.Role.BundleKey)
dSet(d, "read_only", role.Role.ReadOnly)

rights, err := role.GetRights(nil)
if err != nil {
return diag.Errorf("[%s %s-%s] error while querying %s %ss: %s", labelVcfaRole, operation, origin, labelVcfaRole, labelVcfaRight, err)

}
var assignedRights []interface{}

for _, right := range rights {
assignedRights = append(assignedRights, right.Name)
}
if len(assignedRights) > 0 {
err = d.Set("rights", assignedRights)
if err != nil {
return diag.Errorf("[%s %s-%s] error setting %ss for %s '%s': %s", labelVcfaRole, operation, origin, labelVcfaRight, labelVcfaRole, roleName, err)
}
}
return nil
}

func resourceVcfaRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

roleName := d.Get("name").(string)
orgId := d.Get("org_id").(string)

// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
org, err := vcdClient.GetAdminOrgById(orgId)
if err != nil {
return diag.Errorf("[%s update] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgId, err)
}

role, err := org.GetRoleById(d.Id())
if err != nil {
return diag.Errorf("[%s update] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
}

if d.HasChange("name") || d.HasChange("description") {
role.Role.Name = roleName
role.Role.Description = d.Get("description").(string)
_, err = role.Update()
if err != nil {
return diag.Errorf("[%s update] error updating %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
}
}

inputRights, err := getRights(vcdClient, org, fmt.Sprintf("%s update", labelVcfaRole), d)
if err != nil {
return diag.FromErr(err)
}

if len(inputRights) > 0 {
err = role.UpdateRights(inputRights)
if err != nil {
return diag.Errorf("[%s update] error updating %s '%s' %ss: %s", labelVcfaRole, labelVcfaRole, roleName, labelVcfaRight, err)
}
} else {
currentRights, err := role.GetRights(nil)
if err != nil {
return diag.Errorf("[%s update] error retrieving %s '%s' %ss: %s", labelVcfaRole, labelVcfaRole, roleName, labelVcfaRight, err)
}
if len(currentRights) > 0 {
err = role.RemoveAllRights()
if err != nil {
return diag.Errorf("[%s update] error removing %s '%s' %ss: %s", labelVcfaRole, labelVcfaRole, roleName, labelVcfaRight, err)
}
}
}
return genericVcfaRoleRead(ctx, d, meta, "resource", "update")
}

func resourceVcfaRoleDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

roleName := d.Get("name").(string)
orgId := d.Get("org_id").(string)

// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
org, err := vcdClient.GetAdminOrgById(orgId)
if err != nil {
return diag.Errorf("[%s delete] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgId, err)
}
var role *govcd.Role
identifier := d.Id()
if identifier != "" {
role, err = org.GetRoleById(identifier)
} else {
role, err = org.GetRoleByName(roleName)
}
if err != nil {
return diag.Errorf("[%s delete] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
}
err = role.Delete()
if err != nil {
return diag.Errorf("[%s delete] error deleting %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
}
return nil
}

func resourceVcfaRoleImport(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
resourceURI := strings.Split(d.Id(), ImportSeparator)
if len(resourceURI) != 2 {
return nil, fmt.Errorf("resource name must be specified as org-name%srole-name", ImportSeparator)
}
orgName, roleName := resourceURI[0], resourceURI[1]

vcdClient := meta.(*VCDClient)

// TODO: TM: Change to vcdClient.GetTmOrgByName(orgName), requires implementing Role support for that type
org, err := vcdClient.GetAdminOrgByName(orgName)
if err != nil {
return nil, fmt.Errorf("[%s import] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgName, err)
}

role, err := org.GetRoleByName(roleName)
if err != nil {
return nil, fmt.Errorf("[%s import] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
}
dSet(d, "org_id", org.AdminOrg.ID)
dSet(d, "name", roleName)
dSet(d, "description", role.Role.Description)
dSet(d, "bundle_key", role.Role.BundleKey)
d.SetId(role.Role.ID)
return []*schema.ResourceData{d}, nil
}
Loading