-
Notifications
You must be signed in to change notification settings - Fork 8
/
accountgroups_resource.go
377 lines (339 loc) · 12.9 KB
/
accountgroups_resource.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// accountgroups_resource.go
package accountgroups
import (
"context"
"fmt"
"strconv"
"time"
"github.com/deploymenttheory/go-api-sdk-jamfpro/sdk/jamfpro"
"github.com/deploymenttheory/terraform-provider-jamfpro/internal/client"
"github.com/deploymenttheory/terraform-provider-jamfpro/internal/endpoints/common"
"github.com/deploymenttheory/terraform-provider-jamfpro/internal/endpoints/common/jamfprivileges"
"github.com/deploymenttheory/terraform-provider-jamfpro/internal/endpoints/common/state"
"github.com/deploymenttheory/terraform-provider-jamfpro/internal/waitfor"
util "github.com/deploymenttheory/terraform-provider-jamfpro/internal/helpers/type_assertion"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// ResourceJamfProAccountGroup defines the schema and CRUD operations for managing buildings in Terraform.
func ResourceJamfProAccountGroups() *schema.Resource {
return &schema.Resource{
CreateContext: ResourceJamfProAccountGroupCreate,
ReadContext: ResourceJamfProAccountGroupRead,
UpdateContext: ResourceJamfProAccountGroupUpdate,
DeleteContext: ResourceJamfProAccountGroupDelete,
CustomizeDiff: customDiffAccountGroups,
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(70 * time.Second),
Read: schema.DefaultTimeout(30 * time.Second),
Update: schema.DefaultTimeout(30 * time.Second),
Delete: schema.DefaultTimeout(15 * time.Second),
},
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: "The unique identifier of the account group.",
},
"name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the account group.",
},
"access_level": {
Type: schema.TypeString,
Required: true,
Description: "The access level of the account. This can be either Full Access, scoped to a jamf pro site with Site Access, or scoped to a jamf pro account group with Group Access",
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
v := util.GetString(val)
if v == "Full Access" || v == "Site Access" || v == "Group Access" {
return
}
errs = append(errs, fmt.Errorf("%q must be either 'Full Access' or 'Site Access' or 'Group Access', got: %s", key, v))
return warns, errs
},
},
"privilege_set": {
Type: schema.TypeString,
Optional: true,
Description: "The privilege set assigned to the account.",
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
v := util.GetString(val)
validPrivileges := []string{"Administrator", "Auditor", "Enrollment Only", "Custom"}
for _, validPriv := range validPrivileges {
if v == validPriv {
return // Valid value found, return without error
}
}
errs = append(errs, fmt.Errorf("%q must be one of %v, got: %s", key, validPrivileges, v))
return warns, errs
},
},
"site": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "The site information associated with the account group if access_level is set to Site Access.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeInt,
Optional: true,
Description: "Jamf Pro Site ID. Value defaults to -1 aka not used.",
Default: -1,
},
"name": {
Type: schema.TypeString,
Optional: true,
Description: "Jamf Pro Site Name. Value defaults to 'None' aka not used",
Computed: true,
},
},
},
},
"jss_objects_privileges": {
Type: schema.TypeSet,
Optional: true,
Description: "Privileges related to JSS Objects.",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: jamfprivileges.ValidateJSSObjectsPrivileges,
},
},
"jss_settings_privileges": {
Type: schema.TypeSet,
Optional: true,
Description: "Privileges related to JSS Settings.",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: jamfprivileges.ValidateJSSSettingsPrivileges,
},
},
"jss_actions_privileges": {
Type: schema.TypeSet,
Optional: true,
Description: "Privileges related to JSS Actions.",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: jamfprivileges.ValidateJSSActionsPrivileges,
},
},
"casper_admin_privileges": {
Type: schema.TypeSet,
Optional: true,
Description: "Privileges related to Casper Admin.",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: jamfprivileges.ValidateCasperAdminPrivileges,
},
},
"members": {
Type: schema.TypeList,
Optional: true,
Description: "Members of the account group.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeInt,
Optional: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"identity_server": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "LDAP or IdP server associated with the account group.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeInt,
Optional: true,
Description: "ID is the ID of the LDAP or IdP configuration in Jamf Pro.",
},
},
},
},
},
}
}
// ResourceJamfProAccountGroupCreate is responsible for creating a new Jamf Pro Script in the remote system.
// The function:
// 1. Constructs the attribute data using the provided Terraform configuration.
// 2. Calls the API to create the attribute in Jamf Pro.
// 3. Updates the Terraform state with the ID of the newly created attribute.
// 4. Initiates a read operation to synchronize the Terraform state with the actual state in Jamf Pro.
// ResourceJamfProAccountGroupCreate is responsible for creating a new Jamf Pro Account Group in the remote system.
func ResourceJamfProAccountGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Assert the meta interface to the expected APIClient type
apiclient, ok := meta.(*client.APIClient)
if !ok {
return diag.Errorf("error asserting meta as *client.APIClient")
}
conn := apiclient.Conn
// Initialize variables
var diags diag.Diagnostics
// Construct the resource object
resource, err := constructJamfProAccountGroup(d)
if err != nil {
return diag.FromErr(fmt.Errorf("failed to construct Jamf Pro Account Group: %v", err))
}
// Retry the API call to create the resource in Jamf Pro
var creationResponse *jamfpro.ResponseAccountGroupCreated
err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *retry.RetryError {
var apiErr error
creationResponse, apiErr = conn.CreateAccountGroup(resource)
if apiErr != nil {
return retry.RetryableError(apiErr)
}
// No error, exit the retry loop
return nil
})
if err != nil {
return diag.FromErr(fmt.Errorf("failed to create Jamf Pro Account Group '%s' after retries: %v", resource.Name, err))
}
// Set the resource ID in Terraform state
d.SetId(strconv.Itoa(creationResponse.ID))
// Wait for the resource to be fully available before reading it
checkResourceExists := func(id interface{}) (interface{}, error) {
intID, err := strconv.Atoi(id.(string))
if err != nil {
return nil, fmt.Errorf("error converting ID '%v' to integer: %v", id, err)
}
return apiclient.Conn.GetAccountGroupByID(intID)
}
_, waitDiags := waitfor.ResourceIsAvailable(ctx, d, "Jamf Pro Account Group", strconv.Itoa(creationResponse.ID), checkResourceExists, time.Duration(common.DefaultPropagationTime)*time.Second, apiclient.EnableCookieJar)
if waitDiags.HasError() {
return waitDiags
}
// Read the resource to ensure the Terraform state is up to date
readDiags := ResourceJamfProAccountGroupRead(ctx, d, meta)
if len(readDiags) > 0 {
diags = append(diags, readDiags...)
}
return diags
}
// ResourceJamfProAccountGroupRead is responsible for reading the current state of a Jamf Pro Account Group Resource from the remote system.
// The function:
// 1. Fetches the attribute's current state using its ID. If it fails then obtain attribute's current state using its Name.
// 2. Updates the Terraform state with the fetched data to ensure it accurately reflects the current state in Jamf Pro.
// 3. Handles any discrepancies, such as the attribute being deleted outside of Terraform, to keep the Terraform state synchronized.
func ResourceJamfProAccountGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Initialize API client
apiclient, ok := meta.(*client.APIClient)
if !ok {
return diag.Errorf("error asserting meta as *client.APIClient")
}
conn := apiclient.Conn
// Initialize variables
var diags diag.Diagnostics
resourceID := d.Id()
// Convert resourceID from string to int
resourceIDInt, err := strconv.Atoi(resourceID)
if err != nil {
return diag.FromErr(fmt.Errorf("error converting resource ID '%s' to int: %v", resourceID, err))
}
// Attempt to fetch the resource by ID
resource, err := conn.GetAccountGroupByID(resourceIDInt)
if err != nil {
// Handle not found error or other errors
return state.HandleResourceNotFoundError(err, d)
}
// Update the Terraform state with the fetched data from the resource
diags = updateTerraformState(d, resource)
// Handle any errors and return diagnostics
if len(diags) > 0 {
return diags
}
return nil
}
// ResourceJamfProAccountGroupUpdate is responsible for updating an existing Jamf Pro Account Group on the remote system.
func ResourceJamfProAccountGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Initialize API client
apiclient, ok := meta.(*client.APIClient)
if !ok {
return diag.Errorf("error asserting meta as *client.APIClient")
}
conn := apiclient.Conn
// Initialize variables
var diags diag.Diagnostics
resourceID := d.Id()
// Convert resourceID from string to int
resourceIDInt, err := strconv.Atoi(resourceID)
if err != nil {
return diag.FromErr(fmt.Errorf("error converting resource ID '%s' to int: %v", resourceID, err))
}
// Construct the resource object
resource, err := constructJamfProAccountGroup(d)
if err != nil {
return diag.FromErr(fmt.Errorf("failed to construct Jamf Pro Account Group for update: %v", err))
}
// Update operations with retries
err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutUpdate), func() *retry.RetryError {
_, apiErr := conn.UpdateAccountGroupByID(resourceIDInt, resource)
if apiErr != nil {
// If updating by ID fails, attempt to update by Name
return retry.RetryableError(apiErr)
}
// Successfully updated the resource, exit the retry loop
return nil
})
if err != nil {
return diag.FromErr(fmt.Errorf("failed to update Jamf Pro Account Group '%s' (ID: %s) after retries: %v", resource.Name, resourceID, err))
}
// Read the resource to ensure the Terraform state is up to date
readDiags := ResourceJamfProAccountGroupRead(ctx, d, meta)
if len(readDiags) > 0 {
diags = append(diags, readDiags...)
}
return diags
}
// ResourceJamfProAccountGroupDelete is responsible for deleting a Jamf Pro account group.
func ResourceJamfProAccountGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Initialize API client
apiclient, ok := meta.(*client.APIClient)
if !ok {
return diag.Errorf("error asserting meta as *client.APIClient")
}
conn := apiclient.Conn
// Initialize variables
var diags diag.Diagnostics
resourceID := d.Id()
// Convert resourceID from string to int
resourceIDInt, err := strconv.Atoi(resourceID)
if err != nil {
return diag.FromErr(fmt.Errorf("error converting resource ID '%s' to int: %v", resourceID, err))
}
// Use the retry function for the delete operation with appropriate timeout
err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutDelete), func() *retry.RetryError {
// Attempt to delete by ID
apiErr := conn.DeleteAccountGroupByID(resourceIDInt)
if apiErr != nil {
// If deleting by ID fails, attempt to delete by Name
resourceName := d.Get("name").(string)
apiErrByName := conn.DeleteAccountGroupByName(resourceName)
if apiErrByName != nil {
// If deletion by name also fails, return a retryable error
return retry.RetryableError(apiErrByName)
}
}
// Successfully deleted the resource, exit the retry loop
return nil
})
if err != nil {
return diag.FromErr(fmt.Errorf("failed to delete Jamf Pro Account Group '%s' (ID: %s) after retries: %v", d.Get("name").(string), resourceID, err))
}
// Clear the ID from the Terraform state as the resource has been deleted
d.SetId("")
return diags
}