Skip to content
This repository was archived by the owner on Jun 27, 2021. It is now read-only.

Commit 4151e66

Browse files
authored
Add gsuite_user_attributes resource (#138)
* Add gsuite_user_fields resource to allow adding custom user fields to already existing users' custom schemas. * rename gsuite_user_fields -> gsuite_user_attributes * Fix resourceUserAttributesDelete,now has logic to clean all of the attributes properly; define resourceUserAttributesUpdate in terms of resourceUserAttributesDelete and resourceUserAttributesCreate to take advantage * Add example
1 parent 8f04384 commit 4151e66

File tree

3 files changed

+247
-7
lines changed

3 files changed

+247
-7
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// The below define a custom directory user schema. For additional details on
2+
// possible types of fields, their values, etc see Google's reference documentation:
3+
// https://developers.google.com/admin-sdk/directory/v1/reference/schemas
4+
resource "gsuite_user_schema" "details" {
5+
schema_name = "additional-details" // Required
6+
display_name = "Additional Details" // Optional (default: value of `schema_name`)
7+
8+
// Defines the schema of a specific field. The `field` element may be
9+
// repeated multiple times to define more than a single custom field.
10+
field {
11+
field_type = "PHONE" // Required (valid values: BOOL, DATE, DOUBLE, EMAIL, INT64, PHONE, STRING)
12+
field_name = "internal-phone" // Required
13+
display_name = "Internal Phone" // Optional (default: value of `field_name`)
14+
indexed = true // Optional (default: true)
15+
read_access_type = "ALL_DOMAIN_USERS" // Optional (default: ADMINS_AND_SELF)
16+
multi_valued = false // Optional (default: false)
17+
}
18+
}
19+
20+
data "gsuite_user_attributes" "details" {
21+
phone {
22+
name = "internal-phone"
23+
value = "555-555-5555"
24+
}
25+
}
26+
27+
resource "gsuite_user" "user" {
28+
primary_email = "flast@example.com"
29+
30+
name {
31+
given_name = "First"
32+
family_name = "Last"
33+
}
34+
}
35+
36+
resource "gsuite_user_attributes" "user_attributes" {
37+
primary_email = gsuite_user.user.primary_email
38+
custom_schema = data.gsuite_user_attributes.details.json
39+
}

gsuite/provider.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ func Provider() *schema.Provider {
5555
"gsuite_user_attributes": dataUserAttributes(),
5656
},
5757
ResourcesMap: map[string]*schema.Resource{
58-
"gsuite_domain": resourceDomain(),
59-
"gsuite_group": resourceGroup(),
60-
"gsuite_group_member": resourceGroupMember(),
61-
"gsuite_group_members": resourceGroupMembers(),
62-
"gsuite_group_settings": resourceGroupSettings(),
63-
"gsuite_user": resourceUser(),
64-
"gsuite_user_schema": resourceUserSchema(),
58+
"gsuite_domain": resourceDomain(),
59+
"gsuite_group": resourceGroup(),
60+
"gsuite_group_member": resourceGroupMember(),
61+
"gsuite_group_members": resourceGroupMembers(),
62+
"gsuite_group_settings": resourceGroupSettings(),
63+
"gsuite_user": resourceUser(),
64+
"gsuite_user_attributes": resourceUserAttributes(),
65+
"gsuite_user_schema": resourceUserSchema(),
6566
},
6667
}
6768

gsuite/resource_user_attributes.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package gsuite
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
10+
"github.com/pkg/errors"
11+
directory "google.golang.org/api/admin/directory/v1"
12+
"google.golang.org/api/googleapi"
13+
)
14+
15+
func resourceUserAttributes() *schema.Resource {
16+
return &schema.Resource{
17+
Create: resourceUserAttributesCreate,
18+
Read: resourceUserAttributesRead,
19+
Update: resourceUserAttributesUpdate,
20+
Delete: resourceUserAttributesDelete,
21+
Importer: &schema.ResourceImporter{
22+
State: resourceUserAttributesImporter,
23+
},
24+
25+
Schema: map[string]*schema.Schema{
26+
"primary_email": {
27+
Type: schema.TypeString,
28+
Required: true,
29+
StateFunc: func(val interface{}) string {
30+
return strings.ToLower(val.(string))
31+
},
32+
},
33+
34+
"custom_schema": {
35+
Type: schema.TypeList,
36+
Optional: true,
37+
Elem: &schema.Resource{
38+
Schema: map[string]*schema.Schema{
39+
"name": {
40+
Type: schema.TypeString,
41+
Required: true,
42+
},
43+
"value": {
44+
Type: schema.TypeString,
45+
Required: true,
46+
},
47+
},
48+
},
49+
},
50+
},
51+
}
52+
}
53+
54+
func resourceUserAttributesCreate(d *schema.ResourceData, meta interface{}) error {
55+
config := meta.(*Config)
56+
57+
user := &directory.User{}
58+
59+
if v, ok := d.GetOk("primary_email"); ok {
60+
log.Printf("[DEBUG] Setting %s: %s", "primary_email", v.(string))
61+
user.PrimaryEmail = strings.ToLower(v.(string))
62+
}
63+
64+
customSchemas := map[string]googleapi.RawMessage{}
65+
for i := 0; i < d.Get("custom_schema.#").(int); i++ {
66+
entry := d.Get(fmt.Sprintf("custom_schema.%d", i)).(map[string]interface{})
67+
customSchemas[entry["name"].(string)] = []byte(entry["value"].(string))
68+
69+
log.Printf("[DEBUG] setting entry %v", entry)
70+
}
71+
if len(customSchemas) > 0 {
72+
user.CustomSchemas = customSchemas
73+
}
74+
75+
var err error
76+
var updatedUser *directory.User
77+
err = retry(func() error {
78+
updatedUser, err = config.directory.Users.Patch(user.PrimaryEmail, user).Do()
79+
return err
80+
}, config.TimeoutMinutes)
81+
82+
if err != nil {
83+
return fmt.Errorf("[ERROR] Error creating user fields: %s", err)
84+
}
85+
86+
d.SetId(updatedUser.Id)
87+
log.Printf("[INFO] Created user fields: %s", updatedUser.PrimaryEmail)
88+
return resourceUserAttributesRead(d, meta)
89+
}
90+
91+
func resourceUserAttributesUpdate(d *schema.ResourceData, meta interface{}) error {
92+
/*
93+
Defined in terms of delete + create, because resourceUserAttributesDelete will delete the information from a user by its stored id - even if we're actually changing the primary_email the gsuite_user_attributes resource points to - and resourceUserAttributesCreate will create the attributes for the current primary_email
94+
*/
95+
err := resourceUserAttributesDelete(d, meta)
96+
if err != nil {
97+
return err
98+
}
99+
100+
err = resourceUserAttributesCreate(d, meta)
101+
if err != nil {
102+
return err
103+
}
104+
105+
return resourceUserAttributesRead(d, meta)
106+
}
107+
108+
func resourceUserAttributesRead(d *schema.ResourceData, meta interface{}) error {
109+
config := meta.(*Config)
110+
111+
var user *directory.User
112+
var err error
113+
err = retry(func() error {
114+
user, err = config.directory.Users.Get(d.Id()).Projection("full").Do()
115+
if user != nil && user.Name == nil {
116+
return errors.New("Eventual consistency. Please try again")
117+
}
118+
return err
119+
}, config.TimeoutMinutes)
120+
121+
if err != nil {
122+
return handleNotFoundError(err, d, fmt.Sprintf("User %q", d.Id()))
123+
}
124+
125+
d.SetId(user.Id)
126+
d.Set("primary_email", user.PrimaryEmail)
127+
128+
err, flattenedCustomSchema := flattenCustomSchema(user.CustomSchemas)
129+
if err != nil {
130+
return err
131+
}
132+
133+
if err = d.Set("custom_schema", flattenedCustomSchema); err != nil {
134+
return fmt.Errorf("Error setting custom_schema in state: %s", err.Error())
135+
}
136+
137+
return nil
138+
}
139+
140+
func resourceUserAttributesDelete(d *schema.ResourceData, meta interface{}) error {
141+
config := meta.(*Config)
142+
143+
user := &directory.User{}
144+
145+
customSchemas := map[string]googleapi.RawMessage{}
146+
for i := 0; i < d.Get("custom_schema.#").(int); i++ {
147+
entry := d.Get(fmt.Sprintf("custom_schema.%d", i)).(map[string]interface{})
148+
149+
customAttributes := map[string]interface{}{}
150+
err := json.Unmarshal([]byte(entry["value"].(string)), &customAttributes)
151+
152+
if err != nil {
153+
return fmt.Errorf("Error unmarshalling custom attributes in resource: %s", err)
154+
}
155+
156+
schemaBody := "{"
157+
for field := range customAttributes {
158+
schemaBody = schemaBody + fmt.Sprintf("\n \"%s\": null,", field)
159+
}
160+
// remove the training comma
161+
schemaBody = schemaBody[:len(schemaBody)-1] + "\n}"
162+
163+
customSchemas[entry["name"].(string)] = []byte(schemaBody)
164+
}
165+
user.CustomSchemas = customSchemas
166+
167+
var err error
168+
err = retry(func() error {
169+
_, err = config.directory.Users.Patch(d.Id(), user).Do()
170+
return err
171+
}, config.TimeoutMinutes)
172+
if err != nil {
173+
return fmt.Errorf("Error deleting user fields: %s", err)
174+
}
175+
176+
d.SetId("")
177+
return nil
178+
}
179+
180+
// Allow importing using any key (id, email, alias)
181+
func resourceUserAttributesImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
182+
config := meta.(*Config)
183+
184+
id, err := config.directory.Users.Get(d.Id()).Projection("full").Do()
185+
186+
if err != nil {
187+
return nil, fmt.Errorf("Error fetching user fields. Make sure the user fields exist: %s ", err)
188+
}
189+
190+
d.SetId(id.Id)
191+
192+
err, flattenedCustomSchema := flattenCustomSchema(id.CustomSchemas)
193+
if err != nil {
194+
return []*schema.ResourceData{d}, err
195+
}
196+
197+
d.Set("custom_schema", flattenedCustomSchema)
198+
199+
return []*schema.ResourceData{d}, nil
200+
}

0 commit comments

Comments
 (0)