Skip to content

Commit f4148bd

Browse files
committed
Add support for the wait_for_completed flag to aap_job resource
Letting the user wait on aap_job resources to complete before continuing creating aap resources. Extra parameters allow the user to tweak the timeout and poll interval of this wait operation
1 parent 7ff276d commit f4148bd

File tree

3 files changed

+119
-9
lines changed

3 files changed

+119
-9
lines changed

docs/resources/job.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ description: |-
44
Launches an AAP job.
55
A job is launched only when the resource is first created or when the resource is changed. The triggers argument can be used to launch a new job based on any arbitrary value.
66
This resource always creates a new job in AAP. A destroy will not delete a job created by this resource, it will only remove the resource from the state.
7+
Moreover, you can set wait_for_completion to true, then Terraform will wait until this job is created and reaches any final state before continuing. This parameter works in both create and update operations.
8+
You can also tweak wait_for_completion_poll_interval_seconds and wait_for_completion_timeout_seconds to control the timeout limit and poll interval.
79
---
810

911
# aap_job (Resource)
@@ -14,6 +16,10 @@ A job is launched only when the resource is first created or when the resource i
1416

1517
This resource always creates a new job in AAP. A destroy will not delete a job created by this resource, it will only remove the resource from the state.
1618

19+
Moreover, you can set `wait_for_completion` to true, then Terraform will wait until this job is created and reaches any final state before continuing. This parameter works in both create and update operations.
20+
21+
You can also tweak `wait_for_completion_poll_interval_seconds` and `wait_for_completion_timeout_seconds` to control the timeout limit and poll interval.
22+
1723
-> **Note** To pass an inventory to an aap_job resource, the underlying job template *must* have been conigured to prompt for the inventory on launch.
1824

1925

@@ -118,6 +124,9 @@ output "job_xyz" {
118124
- `extra_vars` (String) Extra Variables. Must be provided as either a JSON or YAML string.
119125
- `inventory_id` (Number) Identifier for the inventory where job should be created in. If not provided, the job will be created in the default inventory.
120126
- `triggers` (Map of String) Map of arbitrary keys and values that, when changed, will trigger a creation of a new Job on AAP. Use 'terraform taint' if you want to force the creation of a new job without changing this value.
127+
- `wait_for_completion` (Boolean) When this is set to `true`, Terraform will wait until this aap_job resource is created, reaches any final status and then, proceeds with the following resource operation
128+
- `wait_for_completion_poll_interval_seconds` (Number) Sets the poll interval in seconds to wait between aap_job status updates. Default value of `5.0`
129+
- `wait_for_completion_timeout_seconds` (Number) Sets the maximum amount of seconds Terraform will wait before timing out the updates, and the job creation will fail. Default value of `120`
121130

122131
### Read-Only
123132

internal/provider/job_resource.go

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import (
77
"fmt"
88
"net/http"
99
"path"
10+
"time"
1011

1112
"github.com/ansible/terraform-provider-aap/internal/provider/customtypes"
1213
"github.com/hashicorp/terraform-plugin-framework/attr"
1314
"github.com/hashicorp/terraform-plugin-framework/diag"
1415
"github.com/hashicorp/terraform-plugin-framework/resource"
1516
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
18+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default"
19+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
1620
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
1721
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1822
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -31,14 +35,17 @@ type JobAPIModel struct {
3135

3236
// JobResourceModel maps the resource schema data.
3337
type JobResourceModel struct {
34-
TemplateID types.Int64 `tfsdk:"job_template_id"`
35-
Type types.String `tfsdk:"job_type"`
36-
URL types.String `tfsdk:"url"`
37-
Status types.String `tfsdk:"status"`
38-
InventoryID types.Int64 `tfsdk:"inventory_id"`
39-
ExtraVars customtypes.AAPCustomStringValue `tfsdk:"extra_vars"`
40-
IgnoredFields types.List `tfsdk:"ignored_fields"`
41-
Triggers types.Map `tfsdk:"triggers"`
38+
TemplateID types.Int64 `tfsdk:"job_template_id"`
39+
Type types.String `tfsdk:"job_type"`
40+
URL types.String `tfsdk:"url"`
41+
Status types.String `tfsdk:"status"`
42+
InventoryID types.Int64 `tfsdk:"inventory_id"`
43+
ExtraVars customtypes.AAPCustomStringValue `tfsdk:"extra_vars"`
44+
IgnoredFields types.List `tfsdk:"ignored_fields"`
45+
Triggers types.Map `tfsdk:"triggers"`
46+
WaitForCompletion types.Bool `tfsdk:"wait_for_completion"`
47+
WaitForCompletionPollInterval types.Float64 `tfsdk:"wait_for_completion_poll_interval_seconds"`
48+
WaitForCompletionTimeout types.Int64 `tfsdk:"wait_for_completion_timeout_seconds"`
4249
}
4350

4451
// JobResource is the resource implementation.
@@ -61,6 +68,46 @@ func NewJobResource() resource.Resource {
6168
return &JobResource{}
6269
}
6370

71+
func waitForJobToReachAnyFinalStatus(client ProviderHTTPClient, model JobResourceModel, diagnostics diag.Diagnostics) error {
72+
isFinalStatus := func(status string) bool {
73+
finalStatuses := map[string]bool{
74+
"new": false,
75+
"pending": false,
76+
"waiting": false,
77+
"running": false,
78+
"successful": true,
79+
"failed": true,
80+
"error": true,
81+
"canceled": true,
82+
}
83+
result, isPresent := finalStatuses[status]
84+
return isPresent && result
85+
}
86+
87+
pollInterval := time.Duration(model.WaitForCompletionPollInterval.ValueFloat64()) * time.Second
88+
timeout := time.Duration(model.WaitForCompletionTimeout.ValueInt64()) * time.Second
89+
startTime := time.Now()
90+
for {
91+
responseBody, err := client.Get(model.URL.ValueString())
92+
diagnostics.Append(model.ParseHttpResponse(responseBody)...)
93+
if err != nil {
94+
return fmt.Errorf("error fetching job status: %s", err)
95+
}
96+
97+
fmt.Printf("Job ID: %s, Current Status: %s\n", model.TemplateID, model.Status.ValueString())
98+
99+
if isFinalStatus(model.Status.ValueString()) {
100+
return nil
101+
}
102+
103+
if time.Since(startTime) > timeout {
104+
return fmt.Errorf("polling timeout reached for job ID: %s", model.TemplateID)
105+
}
106+
107+
time.Sleep(pollInterval)
108+
}
109+
}
110+
64111
// Metadata returns the resource type name.
65112
func (r *JobResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
66113
resp.TypeName = req.ProviderTypeName + "_job"
@@ -132,14 +179,39 @@ func (r *JobResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *
132179
Computed: true,
133180
Description: "The list of properties set by the user but ignored on server side.",
134181
},
182+
"wait_for_completion": schema.BoolAttribute{
183+
Optional: true,
184+
Computed: true,
185+
Default: booldefault.StaticBool(false),
186+
Description: "When this is set to `true`, Terraform will wait until this aap_job resource is created, reaches " +
187+
"any final status and then, proceeds with the following resource operation",
188+
},
189+
"wait_for_completion_poll_interval_seconds": schema.Float64Attribute{
190+
Optional: true,
191+
Computed: true,
192+
Default: float64default.StaticFloat64(5.0),
193+
Description: "Sets the poll interval in seconds to wait between aap_job status updates. Default value of `5.0`",
194+
},
195+
"wait_for_completion_timeout_seconds": schema.Int64Attribute{
196+
Optional: true,
197+
Computed: true,
198+
Default: int64default.StaticInt64(120),
199+
Description: "Sets the maximum amount of seconds Terraform will wait before timing out the updates, " +
200+
"and the job creation will fail. Default value of `120`",
201+
},
135202
},
136203
MarkdownDescription: "Launches an AAP job.\n\n" +
137204
"A job is launched only when the resource is first created or when the " +
138205
"resource is changed. The " + "`triggers`" + " argument can be used to " +
139206
"launch a new job based on any arbitrary value.\n\n" +
140207
"This resource always creates a new job in AAP. A destroy will not " +
141208
"delete a job created by this resource, it will only remove the resource " +
142-
"from the state.",
209+
"from the state.\n\n" +
210+
"Moreover, you can set `wait_for_completion` to true, then Terraform will " +
211+
"wait until this job is created and reaches any final state before continuing. " +
212+
"This parameter works in both create and update operations.\n\n" +
213+
"You can also tweak `wait_for_completion_poll_interval_seconds` and " +
214+
"`wait_for_completion_timeout_seconds` to control the timeout limit and poll interval.",
143215
}
144216
}
145217

@@ -157,6 +229,15 @@ func (r *JobResource) Create(ctx context.Context, req resource.CreateRequest, re
157229
return
158230
}
159231

232+
// If the job was configured to wait for completion, start polling the job status
233+
// and wait for it to complete before marking the resource as created
234+
if data.WaitForCompletion.ValueBool() {
235+
waitForJobToReachAnyFinalStatus(r.client, data, resp.Diagnostics)
236+
if resp.Diagnostics.HasError() {
237+
return
238+
}
239+
}
240+
160241
// Save updated data into Terraform state
161242
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
162243
if resp.Diagnostics.HasError() {
@@ -210,6 +291,15 @@ func (r *JobResource) Update(ctx context.Context, req resource.UpdateRequest, re
210291
return
211292
}
212293

294+
// If the job was configured to wait for completion, start polling the job status
295+
// and wait for it to complete before marking the resource as created
296+
if data.WaitForCompletion.ValueBool() {
297+
waitForJobToReachAnyFinalStatus(r.client, data, resp.Diagnostics)
298+
if resp.Diagnostics.HasError() {
299+
return
300+
}
301+
}
302+
213303
// Save updated data into Terraform state
214304
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
215305
if resp.Diagnostics.HasError() {

internal/provider/job_resource_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ func TestJobResourceCreateRequestBody(t *testing.T) {
100100
},
101101
expected: []byte(`{"inventory": 3}`),
102102
},
103+
{
104+
name: "wait_for_completed parameters",
105+
input: JobResourceModel{
106+
InventoryID: basetypes.NewInt64Value(3),
107+
TemplateID: types.Int64Value(1),
108+
WaitForCompletion: basetypes.NewBoolValue(true),
109+
WaitForCompletionPollInterval: basetypes.NewFloat64Value(2.0),
110+
WaitForCompletionTimeout: basetypes.NewInt64Value(60),
111+
},
112+
expected: []byte(`{"inventory":3}`),
113+
},
103114
}
104115

105116
for _, tc := range testTable {

0 commit comments

Comments
 (0)