-
Notifications
You must be signed in to change notification settings - Fork 8
/
scripts_resource.go
310 lines (277 loc) · 10.4 KB
/
scripts_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
// scripts_resource.go
package scripts
import (
"context"
"fmt"
"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/state"
"github.com/deploymenttheory/terraform-provider-jamfpro/internal/waitfor"
"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"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
// ResourceJamfProScripts defines the schema and CRUD operations for managing Jamf Pro Scripts in Terraform.
func ResourceJamfProScripts() *schema.Resource {
return &schema.Resource{
CreateContext: ResourceJamfProScriptsCreate,
ReadContext: ResourceJamfProScriptsRead,
UpdateContext: ResourceJamfProScriptsUpdate,
DeleteContext: ResourceJamfProScriptsDelete,
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),
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: "The Jamf Pro unique identifier (ID) of the script.",
},
"name": {
Type: schema.TypeString,
Required: true,
Description: "Display name for the script.",
},
"category_id": {
Type: schema.TypeString,
Optional: true,
Description: "The Jamf Pro unique identifier (ID) of the category.",
},
"category_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Name of the category to add the script to.",
},
"info": {
Type: schema.TypeString,
Optional: true,
Description: "Information to display to the administrator when the script is run.",
},
"notes": {
Type: schema.TypeString,
Optional: true,
Description: "Notes to display about the script (e.g., who created it and when it was created).",
},
"os_requirements": {
Type: schema.TypeString,
Optional: true,
Description: "The script can only be run on computers with these operating system versions. Each version must be separated by a comma (e.g., 10.11, 15, 16.1).",
},
"priority": {
Type: schema.TypeString,
Required: true,
Description: "Execution priority of the script (BEFORE, AFTER, AT_REBOOT).",
ValidateFunc: validation.StringInSlice([]string{"BEFORE", "AFTER", "AT_REBOOT"}, false),
},
"script_contents": {
Type: schema.TypeString,
Required: true,
Description: "Contents of the script. Must be non-compiled and in an accepted format.",
},
"parameter4": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 4",
},
"parameter5": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 5",
},
"parameter6": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 6",
},
"parameter7": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 7",
},
"parameter8": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 8",
},
"parameter9": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 9",
},
"parameter10": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 10",
},
"parameter11": {
Type: schema.TypeString,
Optional: true,
Description: "Script parameter label 11",
},
},
}
}
// ResourceJamfProScriptsCreate is responsible for creating a new Jamf Pro Script in the remote system.
// The function:
// 1. Constructs the script data using the provided Terraform configuration.
// 2. Calls the API to create the script in Jamf Pro.
// 3. Updates the Terraform state with the ID of the newly created script.
// 4. Initiates a read operation to synchronize the Terraform state with the actual state in Jamf Pro.
func ResourceJamfProScriptsCreate(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 := constructJamfProScript(d)
if err != nil {
return diag.FromErr(fmt.Errorf("failed to construct Jamf Pro Script: %v", err))
}
// Retry the API call to create the resource in Jamf Pro
var creationResponse *jamfpro.ResponseScriptCreate
err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *retry.RetryError {
var apiErr error
creationResponse, apiErr = conn.CreateScript(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 Script '%s' after retries: %v", resource.Name, err))
}
// Set the resource ID in Terraform state
d.SetId(creationResponse.ID)
// Wait for the resource to be fully available before reading it
checkResourceExists := func(id interface{}) (interface{}, error) {
return apiclient.Conn.GetScriptByID(id.(string))
}
_, waitDiags := waitfor.ResourceIsAvailable(ctx, d, "Jamf Pro Script", 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 := ResourceJamfProScriptsRead(ctx, d, meta)
if len(readDiags) > 0 {
return readDiags
}
return diags
}
// ResourceJamfProScriptsRead is responsible for reading the current state of a Jamf Pro Script Resource from the remote system.
// The function:
// 1. Fetches the script's current state using its ID. If it fails then obtain script'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 script being deleted outside of Terraform, to keep the Terraform state synchronized.
func ResourceJamfProScriptsRead(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")
}
// Initialize variables
resourceID := d.Id()
var diags diag.Diagnostics
// Attempt to fetch the resource by ID
resource, err := apiclient.Conn.GetScriptByID(resourceID)
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
}
// ResourceJamfProScriptsUpdate is responsible for updating an existing Jamf Pro Department on the remote system.
func ResourceJamfProScriptsUpdate(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()
// Construct the resource object
resource, err := constructJamfProScript(d)
if err != nil {
return diag.FromErr(fmt.Errorf("failed to construct Jamf Pro Script for update: %v", err))
}
// Update operation with retries
err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutUpdate), func() *retry.RetryError {
_, apiErr := conn.UpdateScriptByID(resourceID, resource)
if apiErr != nil {
// If updating by ID fails, attempt to update by Name
resourceName := d.Get("name").(string)
_, apiErrByName := conn.UpdateScriptByName(resourceName, resource)
if apiErrByName != nil {
// If updating by name also fails, return a retryable error
return retry.RetryableError(apiErrByName)
}
}
// Successfully updated the resource, exit the retry loop
return nil
})
if err != nil {
return diag.FromErr(fmt.Errorf("failed to update Jamf Pro Script '%s' (ID: %s) after retries: %v", d.Get("name").(string), resourceID, err))
}
// Read the resource to ensure the Terraform state is up to date
readDiags := ResourceJamfProScriptsRead(ctx, d, meta)
if len(readDiags) > 0 {
diags = append(diags, readDiags...)
}
return diags
}
// ResourceJamfProScriptsDelete is responsible for deleting a Jamf Pro Department.
func ResourceJamfProScriptsDelete(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()
// 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 the resource by ID
apiErr := conn.DeleteScriptByID(resourceID)
if apiErr != nil {
// If deleting by ID fails, attempt to delete by Name
resourceName := d.Get("name").(string)
apiErrByName := conn.DeleteScriptByName(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 Script '%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
}