Skip to content

Commit 110cde6

Browse files
authored
Add vcfa_role resource and data source (#19)
Signed-off-by: abarreiro <abarreiro@vmware.com>
1 parent 4da4451 commit 110cde6

File tree

9 files changed

+694
-5
lines changed

9 files changed

+694
-5
lines changed

.changes/v1.0.0/19-features.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* **New Resource:** `vcfa_role` to manage Roles [GH-19]
2+
* **New Data Source:** `vcfa_role` to read existing Roles [GH-19]

vcfa/datasource_vcfa_role.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package vcfa
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
func datasourceVcfaRole() *schema.Resource {
12+
return &schema.Resource{
13+
ReadContext: datasourceVcfaRoleRead,
14+
Schema: map[string]*schema.Schema{
15+
"name": {
16+
Type: schema.TypeString,
17+
Required: true,
18+
Description: fmt.Sprintf("Name of the %s", labelVcfaRole),
19+
},
20+
"org_id": {
21+
Type: schema.TypeString,
22+
Required: true,
23+
Description: fmt.Sprintf("The ID of the %s of the %s", labelVcfaOrg, labelVcfaRole),
24+
},
25+
"description": {
26+
Type: schema.TypeString,
27+
Computed: true,
28+
Description: fmt.Sprintf("%s description", labelVcfaRole),
29+
},
30+
"bundle_key": {
31+
Type: schema.TypeString,
32+
Computed: true,
33+
Description: "Key used for internationalization",
34+
},
35+
"read_only": {
36+
Type: schema.TypeBool,
37+
Computed: true,
38+
Description: fmt.Sprintf("Whether this %s is read-only", labelVcfaRole),
39+
},
40+
"rights": {
41+
Type: schema.TypeSet,
42+
Computed: true,
43+
Description: fmt.Sprintf("Set of %ss assigned to this %s", labelVcfaRight, labelVcfaRole),
44+
Elem: &schema.Schema{Type: schema.TypeString},
45+
},
46+
},
47+
}
48+
}
49+
50+
func datasourceVcfaRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
51+
return genericVcfaRoleRead(ctx, d, meta, "datasource", "read")
52+
}

vcfa/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var globalDataSourceMap = map[string]*schema.Resource{
5555
"vcfa_org_oidc": datasourceVcfaOrgOidc(), // 1.0
5656
"vcfa_right": datasourceVcfaRight(), // 1.0
5757
"vcfa_rights_bundle": datasourceVcfaRightsBundle(), // 1.0
58+
"vcfa_role": datasourceVcfaRole(), // 1.0
5859
}
5960

6061
var globalResourceMap = map[string]*schema.Resource{
@@ -72,6 +73,7 @@ var globalResourceMap = map[string]*schema.Resource{
7273
"vcfa_org_regional_networking": resourceVcfaOrgRegionalNetworking(), // 1.0
7374
"vcfa_org_oidc": resourceVcfaOrgOidc(), // 1.0
7475
"vcfa_rights_bundle": resourceVcfaRightsBundle(), // 1.0
76+
"vcfa_role": resourceVcfaRole(), // 1.0
7577
}
7678

7779
// Provider returns a terraform.ResourceProvider.

vcfa/resource_vcfa_rights_bundle.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func resourceVcfaRightsBundleCreate(ctx context.Context, d *schema.ResourceData,
6969
rightsBundleName := d.Get("name").(string)
7070
publishToAllTenants := d.Get("publish_to_all_orgs").(bool)
7171

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

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

342+
var right *types.Right
343+
var err error
344+
341345
for _, r := range rights {
342346
rn := r.(string)
343-
right, err := client.Client.GetRightByName(rn)
347+
if org != nil {
348+
right, err = org.GetRightByName(rn)
349+
} else {
350+
right, err = client.Client.GetRightByName(rn)
351+
}
344352
if err != nil {
345-
return nil, fmt.Errorf("[%s] error retrieving %s %s: %s", label, labelVcfaRight, rn, err)
353+
return nil, fmt.Errorf("[%s] error retrieving %s '%s': %s", label, labelVcfaRight, rn, err)
346354
}
347355
inputRights = append(inputRights, types.OpenApiReference{Name: rn, ID: right.ID})
348356
}

vcfa/resource_vcfa_role.go

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
package vcfa
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/vmware/go-vcloud-director/v3/govcd"
11+
"github.com/vmware/go-vcloud-director/v3/types/v56"
12+
)
13+
14+
const labelVcfaRole = "Role"
15+
16+
func resourceVcfaRole() *schema.Resource {
17+
return &schema.Resource{
18+
CreateContext: resourceVcfaRoleCreate,
19+
ReadContext: resourceVcfaRoleRead,
20+
UpdateContext: resourceVcfaRoleUpdate,
21+
DeleteContext: resourceVcfaRoleDelete,
22+
Importer: &schema.ResourceImporter{
23+
StateContext: resourceVcfaRoleImport,
24+
},
25+
Schema: map[string]*schema.Schema{
26+
"name": {
27+
Type: schema.TypeString,
28+
Required: true,
29+
Description: fmt.Sprintf("Name of the %s", labelVcfaRole),
30+
},
31+
"org_id": {
32+
Type: schema.TypeString,
33+
Required: true,
34+
ForceNew: true,
35+
Description: fmt.Sprintf("The ID of the %s of the %s", labelVcfaOrg, labelVcfaRole),
36+
},
37+
"description": {
38+
Type: schema.TypeString,
39+
Required: true,
40+
Description: fmt.Sprintf("%s description", labelVcfaRole),
41+
},
42+
"bundle_key": {
43+
Type: schema.TypeString,
44+
Computed: true,
45+
Description: "Key used for internationalization",
46+
},
47+
"read_only": {
48+
Type: schema.TypeBool,
49+
Computed: true,
50+
Description: fmt.Sprintf("Whether this %s is read-only", labelVcfaRole),
51+
},
52+
"rights": {
53+
Type: schema.TypeSet,
54+
Optional: true,
55+
Description: fmt.Sprintf("Set of %ss assigned to this %s", labelVcfaRight, labelVcfaRole),
56+
Elem: &schema.Schema{Type: schema.TypeString},
57+
},
58+
},
59+
}
60+
}
61+
62+
func resourceVcfaRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
63+
vcdClient := meta.(*VCDClient)
64+
65+
roleName := d.Get("name").(string)
66+
orgId := d.Get("org_id").(string)
67+
68+
// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
69+
org, err := vcdClient.GetAdminOrgById(orgId)
70+
if err != nil {
71+
return diag.Errorf("[%s create] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgId, err)
72+
}
73+
74+
// Check rights early, so that we can show a friendly error message when there are missing implied rights
75+
inputRights, err := getRights(vcdClient, org, fmt.Sprintf("%s create", labelVcfaRole), d)
76+
if err != nil {
77+
return diag.FromErr(err)
78+
}
79+
80+
role, err := org.CreateRole(&types.Role{
81+
Name: roleName,
82+
Description: d.Get("description").(string),
83+
BundleKey: types.VcloudUndefinedKey,
84+
})
85+
if err != nil {
86+
return diag.Errorf("[%s create] error creating %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
87+
}
88+
if len(inputRights) > 0 {
89+
err = role.AddRights(inputRights)
90+
if err != nil {
91+
return diag.Errorf("[%s create] error adding %ss to %s '%s': %s", labelVcfaRole, labelVcfaRight, labelVcfaRole, roleName, err)
92+
}
93+
}
94+
95+
d.SetId(role.Role.ID)
96+
return genericVcfaRoleRead(ctx, d, meta, "resource", "create")
97+
}
98+
99+
func resourceVcfaRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
100+
return genericVcfaRoleRead(ctx, d, meta, "resource", "read")
101+
}
102+
103+
func genericVcfaRoleRead(_ context.Context, d *schema.ResourceData, meta interface{}, origin, operation string) diag.Diagnostics {
104+
vcdClient := meta.(*VCDClient)
105+
106+
roleName := d.Get("name").(string)
107+
orgId := d.Get("org_id").(string)
108+
identifier := d.Id()
109+
110+
var role *govcd.Role
111+
var err error
112+
113+
// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
114+
org, err := vcdClient.GetAdminOrgById(orgId)
115+
if err != nil {
116+
return diag.Errorf("[%s %s-%s] error retrieving %s '%s': %s", labelVcfaRole, operation, origin, labelVcfaOrg, orgId, err)
117+
}
118+
119+
if identifier == "" {
120+
role, err = org.GetRoleByName(roleName)
121+
} else {
122+
role, err = org.GetRoleById(identifier)
123+
}
124+
if err != nil {
125+
if origin == "resource" && govcd.ContainsNotFound(err) {
126+
d.SetId("")
127+
return nil
128+
}
129+
return diag.FromErr(err)
130+
}
131+
132+
d.SetId(role.Role.ID)
133+
dSet(d, "description", role.Role.Description)
134+
dSet(d, "bundle_key", role.Role.BundleKey)
135+
dSet(d, "read_only", role.Role.ReadOnly)
136+
137+
rights, err := role.GetRights(nil)
138+
if err != nil {
139+
return diag.Errorf("[%s %s-%s] error while querying %s %ss: %s", labelVcfaRole, operation, origin, labelVcfaRole, labelVcfaRight, err)
140+
141+
}
142+
var assignedRights []interface{}
143+
144+
for _, right := range rights {
145+
assignedRights = append(assignedRights, right.Name)
146+
}
147+
if len(assignedRights) > 0 {
148+
err = d.Set("rights", assignedRights)
149+
if err != nil {
150+
return diag.Errorf("[%s %s-%s] error setting %ss for %s '%s': %s", labelVcfaRole, operation, origin, labelVcfaRight, labelVcfaRole, roleName, err)
151+
}
152+
}
153+
return nil
154+
}
155+
156+
func resourceVcfaRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
157+
vcdClient := meta.(*VCDClient)
158+
159+
roleName := d.Get("name").(string)
160+
orgId := d.Get("org_id").(string)
161+
162+
// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
163+
org, err := vcdClient.GetAdminOrgById(orgId)
164+
if err != nil {
165+
return diag.Errorf("[%s update] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgId, err)
166+
}
167+
168+
role, err := org.GetRoleById(d.Id())
169+
if err != nil {
170+
return diag.Errorf("[%s update] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
171+
}
172+
173+
if d.HasChange("name") || d.HasChange("description") {
174+
role.Role.Name = roleName
175+
role.Role.Description = d.Get("description").(string)
176+
_, err = role.Update()
177+
if err != nil {
178+
return diag.Errorf("[%s update] error updating %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
179+
}
180+
}
181+
182+
inputRights, err := getRights(vcdClient, org, fmt.Sprintf("%s update", labelVcfaRole), d)
183+
if err != nil {
184+
return diag.FromErr(err)
185+
}
186+
187+
if len(inputRights) > 0 {
188+
err = role.UpdateRights(inputRights)
189+
if err != nil {
190+
return diag.Errorf("[%s update] error updating %s '%s' %ss: %s", labelVcfaRole, labelVcfaRole, roleName, labelVcfaRight, err)
191+
}
192+
} else {
193+
currentRights, err := role.GetRights(nil)
194+
if err != nil {
195+
return diag.Errorf("[%s update] error retrieving %s '%s' %ss: %s", labelVcfaRole, labelVcfaRole, roleName, labelVcfaRight, err)
196+
}
197+
if len(currentRights) > 0 {
198+
err = role.RemoveAllRights()
199+
if err != nil {
200+
return diag.Errorf("[%s update] error removing %s '%s' %ss: %s", labelVcfaRole, labelVcfaRole, roleName, labelVcfaRight, err)
201+
}
202+
}
203+
}
204+
return genericVcfaRoleRead(ctx, d, meta, "resource", "update")
205+
}
206+
207+
func resourceVcfaRoleDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
208+
vcdClient := meta.(*VCDClient)
209+
210+
roleName := d.Get("name").(string)
211+
orgId := d.Get("org_id").(string)
212+
213+
// TODO: TM: Change to vcdClient.GetTmOrgById(orgId), requires implementing Role support for that type
214+
org, err := vcdClient.GetAdminOrgById(orgId)
215+
if err != nil {
216+
return diag.Errorf("[%s delete] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgId, err)
217+
}
218+
var role *govcd.Role
219+
identifier := d.Id()
220+
if identifier != "" {
221+
role, err = org.GetRoleById(identifier)
222+
} else {
223+
role, err = org.GetRoleByName(roleName)
224+
}
225+
if err != nil {
226+
return diag.Errorf("[%s delete] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
227+
}
228+
err = role.Delete()
229+
if err != nil {
230+
return diag.Errorf("[%s delete] error deleting %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
231+
}
232+
return nil
233+
}
234+
235+
func resourceVcfaRoleImport(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
236+
resourceURI := strings.Split(d.Id(), ImportSeparator)
237+
if len(resourceURI) != 2 {
238+
return nil, fmt.Errorf("resource name must be specified as org-name%srole-name", ImportSeparator)
239+
}
240+
orgName, roleName := resourceURI[0], resourceURI[1]
241+
242+
vcdClient := meta.(*VCDClient)
243+
244+
// TODO: TM: Change to vcdClient.GetTmOrgByName(orgName), requires implementing Role support for that type
245+
org, err := vcdClient.GetAdminOrgByName(orgName)
246+
if err != nil {
247+
return nil, fmt.Errorf("[%s import] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaOrg, orgName, err)
248+
}
249+
250+
role, err := org.GetRoleByName(roleName)
251+
if err != nil {
252+
return nil, fmt.Errorf("[%s import] error retrieving %s '%s': %s", labelVcfaRole, labelVcfaRole, roleName, err)
253+
}
254+
dSet(d, "org_id", org.AdminOrg.ID)
255+
dSet(d, "name", roleName)
256+
dSet(d, "description", role.Role.Description)
257+
dSet(d, "bundle_key", role.Role.BundleKey)
258+
d.SetId(role.Role.ID)
259+
return []*schema.ResourceData{d}, nil
260+
}

0 commit comments

Comments
 (0)