Skip to content

Commit f9ad392

Browse files
authored
vcfa_org_local_user resource + data source (#25)
1 parent c707066 commit f9ad392

File tree

11 files changed

+515
-4
lines changed

11 files changed

+515
-4
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- **New Resource:** `vcfa_org_local_user` to manage Org Local users [GH-25]
2+
- **New Data Source:** `vcfa_org_local_user` to read Org Local users [GH-25]

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
77
github.com/hashicorp/go-version v1.7.0
88
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0
9-
github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.23
9+
github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.24
1010
)
1111

1212
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
149149
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
150150
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
151151
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
152-
github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.23 h1:GhQhlULBs/7oetCZ2IFvQfH7OqEo6Q5r9GT6v5YgZOQ=
153-
github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.23/go.mod h1:68KHsVns52dsq/w5JQYzauaU/+NAi1FmCxhBrFc/VoQ=
152+
github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.24 h1:WarOa7cskCyAsbMgtDGc2pxsDW2hnWkxdgz0AhOQS1o=
153+
github.com/vmware/go-vcloud-director/v3 v3.0.0-alpha.24/go.mod h1:68KHsVns52dsq/w5JQYzauaU/+NAi1FmCxhBrFc/VoQ=
154154
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
155155
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
156156
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
"github.com/vmware/go-vcloud-director/v3/govcd"
10+
"github.com/vmware/go-vcloud-director/v3/types/v56"
11+
)
12+
13+
func datasourceVcfaLocalUser() *schema.Resource {
14+
return &schema.Resource{
15+
ReadContext: datasourceVcfaLocalUserRead,
16+
17+
Schema: map[string]*schema.Schema{
18+
"org_id": {
19+
Type: schema.TypeString,
20+
Required: true,
21+
Description: fmt.Sprintf("Parent %s ID for %s", labelVcfaOrg, labelLocalUser),
22+
},
23+
"username": {
24+
Type: schema.TypeString,
25+
Required: true,
26+
Description: fmt.Sprintf("%s User Name", labelLocalUser),
27+
},
28+
"role_ids": {
29+
Type: schema.TypeSet,
30+
Computed: true,
31+
Elem: &schema.Schema{Type: schema.TypeString},
32+
Description: fmt.Sprintf("%ss to use for %s", labelVcfaRole, labelLocalUser),
33+
},
34+
},
35+
}
36+
}
37+
38+
func datasourceVcfaLocalUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
39+
vcfaClient := meta.(*VCDClient)
40+
tenantContext, err := getTenantContextFromOrgId(vcfaClient, d.Get("org_id").(string))
41+
if err != nil {
42+
return diag.FromErr(err)
43+
}
44+
45+
getByNameFunc := func(username string) (*govcd.OpenApiUser, error) {
46+
return vcfaClient.GetUserByName(username, tenantContext)
47+
}
48+
49+
c := dsReadConfig[*govcd.OpenApiUser, types.OpenApiUser]{
50+
entityLabel: labelLocalUser,
51+
getEntityFunc: getByNameFunc,
52+
stateStoreFunc: setLocalUserData,
53+
overrideDefaultNameField: "username",
54+
}
55+
return readDatasource(ctx, d, meta, c)
56+
}

vcfa/helpers.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package vcfa
22

33
import (
4+
"os"
5+
46
"github.com/vmware/go-vcloud-director/v3/govcd"
57
"github.com/vmware/go-vcloud-director/v3/util"
6-
"os"
78
)
89

910
// Returns a valid Tenant Context if the Organization identified by the given ID is valid and exists.

vcfa/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ var globalDataSourceMap = map[string]*schema.Resource{
5959
"vcfa_role": datasourceVcfaRole(), // 1.0
6060
"vcfa_global_role": datasourceVcfaGlobalRole(), // 1.0
6161
"vcfa_certificate": datasourceVcfaCertificate(), // 1.0
62+
"vcfa_org_local_user": datasourceVcfaLocalUser(), // 1.0
6263
}
6364

6465
var globalResourceMap = map[string]*schema.Resource{
@@ -81,6 +82,7 @@ var globalResourceMap = map[string]*schema.Resource{
8182
"vcfa_global_role": resourceVcfaGlobalRole(), // 1.0
8283
"vcfa_api_token": resourceVcfaApiToken(), // 1.0
8384
"vcfa_certificate": resourceVcfaCertificate(), // 1.0
85+
"vcfa_org_local_user": resourceVcfaLocalUser(), // 1.0
8486
}
8587

8688
// Provider returns a terraform.ResourceProvider.
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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 labelLocalUser = "Org Local User"
15+
16+
func resourceVcfaLocalUser() *schema.Resource {
17+
return &schema.Resource{
18+
CreateContext: resourceVcfaLocalUserCreate,
19+
ReadContext: resourceVcfaLocalUserRead,
20+
UpdateContext: resourceVcfaLocalUserUpdate,
21+
DeleteContext: resourceVcfaLocalUserDelete,
22+
Importer: &schema.ResourceImporter{
23+
StateContext: resourceVcfaLocalUserImport,
24+
},
25+
26+
Schema: map[string]*schema.Schema{
27+
"org_id": {
28+
Type: schema.TypeString,
29+
Required: true,
30+
ForceNew: true,
31+
Description: fmt.Sprintf("Parent %s ID for %s", labelVcfaOrg, labelLocalUser),
32+
},
33+
"role_ids": {
34+
Type: schema.TypeSet,
35+
Required: true,
36+
Elem: &schema.Schema{Type: schema.TypeString},
37+
Description: fmt.Sprintf("%ss to use for %s", labelVcfaRole, labelLocalUser),
38+
},
39+
"username": {
40+
Type: schema.TypeString,
41+
Required: true,
42+
Description: fmt.Sprintf("%s username", labelLocalUser),
43+
},
44+
"password": {
45+
Type: schema.TypeString,
46+
Required: true,
47+
Sensitive: true,
48+
Description: fmt.Sprintf("Password for %s", labelLocalUser),
49+
},
50+
},
51+
}
52+
}
53+
54+
func resourceVcfaLocalUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
55+
vcfaClient := meta.(*VCDClient)
56+
tenantContext, err := getTenantContextFromOrgId(vcfaClient, d.Get("org_id").(string))
57+
if err != nil {
58+
return diag.FromErr(err)
59+
}
60+
61+
createFunc := func(config *types.OpenApiUser) (*govcd.OpenApiUser, error) {
62+
return vcfaClient.CreateUser(config, tenantContext)
63+
}
64+
65+
c := crudConfig[*govcd.OpenApiUser, types.OpenApiUser]{
66+
entityLabel: labelLocalUser,
67+
getTypeFunc: getLocalUserType,
68+
stateStoreFunc: setLocalUserData,
69+
createFunc: createFunc,
70+
resourceReadFunc: resourceVcfaLocalUserRead,
71+
}
72+
return createResource(ctx, d, meta, c)
73+
}
74+
75+
func resourceVcfaLocalUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
76+
vcfaClient := meta.(*VCDClient)
77+
tenantContext, err := getTenantContextFromOrgId(vcfaClient, d.Get("org_id").(string))
78+
if err != nil {
79+
return diag.FromErr(err)
80+
}
81+
82+
getByIdFunc := func(id string) (*govcd.OpenApiUser, error) {
83+
return vcfaClient.GetUserById(id, tenantContext)
84+
}
85+
86+
c := crudConfig[*govcd.OpenApiUser, types.OpenApiUser]{
87+
entityLabel: labelLocalUser,
88+
getTypeFunc: getLocalUserType,
89+
getEntityFunc: getByIdFunc,
90+
resourceReadFunc: resourceVcfaLocalUserRead,
91+
}
92+
93+
return updateResource(ctx, d, meta, c)
94+
}
95+
96+
func resourceVcfaLocalUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
97+
vcfaClient := meta.(*VCDClient)
98+
tenantContext, err := getTenantContextFromOrgId(vcfaClient, d.Get("org_id").(string))
99+
if err != nil {
100+
if govcd.ContainsNotFound(err) { // Org no longer exists, therefore user is also gone
101+
d.SetId("")
102+
return nil
103+
}
104+
return diag.FromErr(err)
105+
}
106+
107+
getByIdFunc := func(id string) (*govcd.OpenApiUser, error) {
108+
return vcfaClient.GetUserById(id, tenantContext)
109+
}
110+
111+
c := crudConfig[*govcd.OpenApiUser, types.OpenApiUser]{
112+
entityLabel: labelLocalUser,
113+
getEntityFunc: getByIdFunc,
114+
stateStoreFunc: setLocalUserData,
115+
}
116+
return readResource(ctx, d, meta, c)
117+
}
118+
119+
func resourceVcfaLocalUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
120+
vcfaClient := meta.(*VCDClient)
121+
122+
tenantContext, err := getTenantContextFromOrgId(vcfaClient, d.Get("org_id").(string))
123+
if err != nil {
124+
return diag.FromErr(err)
125+
}
126+
getByIdFunc := func(id string) (*govcd.OpenApiUser, error) {
127+
return vcfaClient.GetUserById(id, tenantContext)
128+
}
129+
130+
c := crudConfig[*govcd.OpenApiUser, types.OpenApiUser]{
131+
entityLabel: labelLocalUser,
132+
getEntityFunc: getByIdFunc,
133+
}
134+
135+
return deleteResource(ctx, d, meta, c)
136+
}
137+
138+
func resourceVcfaLocalUserImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
139+
vcdClient := meta.(*VCDClient)
140+
141+
idSlice := strings.Split(d.Id(), ImportSeparator)
142+
if len(idSlice) != 2 {
143+
return nil, fmt.Errorf("expected import ID to be <org name>%s<user name>", ImportSeparator)
144+
}
145+
146+
org, err := vcdClient.GetTmOrgByName(idSlice[0])
147+
if err != nil {
148+
return nil, fmt.Errorf("error getting %s: %s", labelVcfaOrg, err)
149+
}
150+
151+
tenantContext := &govcd.TenantContext{
152+
OrgId: org.TmOrg.ID,
153+
OrgName: org.TmOrg.Name,
154+
}
155+
156+
user, err := vcdClient.GetUserByName(idSlice[1], tenantContext)
157+
if err != nil {
158+
return nil, fmt.Errorf("error retrieving %s: %s", labelLocalUser, err)
159+
}
160+
161+
d.SetId(user.User.ID)
162+
dSet(d, "org_id", org.TmOrg.ID)
163+
164+
return []*schema.ResourceData{d}, nil
165+
}
166+
167+
func getLocalUserType(vcfaClient *VCDClient, d *schema.ResourceData) (*types.OpenApiUser, error) {
168+
org, err := vcfaClient.GetTmOrgById(d.Get("org_id").(string))
169+
if err != nil {
170+
return nil, fmt.Errorf("error getting %s: %s", labelVcfaOrg, err)
171+
}
172+
173+
roleSet := convertSchemaSetToSliceOfStrings(d.Get("role_ids").(*schema.Set))
174+
t := &types.OpenApiUser{
175+
OrgEntityRef: &types.OpenApiReference{ID: d.Get("org_id").(string), Name: org.TmOrg.Name},
176+
Username: d.Get("username").(string),
177+
Password: d.Get("password").(string),
178+
ProviderType: "LOCAL",
179+
RoleEntityRefs: convertSliceOfStringsToOpenApiReferenceIds(roleSet),
180+
Locked: addrOf(false),
181+
}
182+
183+
// Update requires ID being present
184+
if d.Id() != "" { // update operation
185+
t.ID = d.Id()
186+
user, err := vcfaClient.GetUserById(d.Id(), nil)
187+
if err != nil {
188+
return nil, fmt.Errorf("error retrieving %s by ID %s: %s", labelLocalUser, d.Id(), err)
189+
}
190+
// Name In Source must be set to "previous" username when performing update
191+
t.NameInSource = user.User.Username
192+
193+
// if password has not changed - send exactly '******' to prevent updating password just like UI
194+
if !d.HasChange("password") {
195+
t.Password = "******"
196+
}
197+
}
198+
199+
return t, nil
200+
}
201+
202+
func setLocalUserData(_ *VCDClient, d *schema.ResourceData, user *govcd.OpenApiUser) error {
203+
if user == nil || user.User == nil {
204+
return fmt.Errorf("nil user structure")
205+
}
206+
d.SetId(user.User.ID)
207+
dSet(d, "username", user.User.Username)
208+
209+
roleIds := extractIdsFromOpenApiReferences(user.User.RoleEntityRefs)
210+
err := d.Set("role_ids", roleIds)
211+
if err != nil {
212+
return fmt.Errorf("error storing 'role_ids': %s", err)
213+
}
214+
215+
dSet(d, "org_id", "")
216+
if user.User.OrgEntityRef != nil {
217+
dSet(d, "org_id", user.User.OrgEntityRef.ID)
218+
}
219+
// dSet(d, "password", user.User.Password) // password is never returned on read
220+
221+
return nil
222+
}

0 commit comments

Comments
 (0)