Skip to content

Commit 2d9a0c3

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 2d9a0c3

File tree

5 files changed

+127
-11
lines changed

5 files changed

+127
-11
lines changed

docs/resources/job.md

Lines changed: 16 additions & 1 deletion
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_timeout_seconds to control the timeout limit.
79
---
810

911
# aap_job (Resource)
@@ -14,7 +16,11 @@ 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

17-
-> **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.
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_timeout_seconds` to control the timeout limit.
22+
23+
-> **Note** To pass an inventory to an aap_job resource, the underlying job template *must* have been configured to prompt for the inventory on launch.
1824

1925

2026
## Example Usage
@@ -84,6 +90,13 @@ resource "aap_job" "sample_xyz" {
8490
extra_vars = "os: Linux\nautomation: ansible-devel"
8591
}
8692
93+
resource "aap_job" "sample_wait_for_completion" {
94+
job_template_id = 9
95+
inventory_id = aap_inventory.my_inventory.id
96+
wait_for_completion = true
97+
wait_for_completion_timeout_seconds = 120
98+
}
99+
87100
output "job_foo" {
88101
value = aap_job.sample_foo
89102
}
@@ -118,6 +131,8 @@ output "job_xyz" {
118131
- `extra_vars` (String) Extra Variables. Must be provided as either a JSON or YAML string.
119132
- `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.
120133
- `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.
134+
- `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
135+
- `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`
121136

122137
### Read-Only
123138

examples/resources/aap_job/resource.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ resource "aap_job" "sample_xyz" {
6262
extra_vars = "os: Linux\nautomation: ansible-devel"
6363
}
6464

65+
resource "aap_job" "sample_wait_for_completion" {
66+
job_template_id = 9
67+
inventory_id = aap_inventory.my_inventory.id
68+
wait_for_completion = true
69+
wait_for_completion_timeout_seconds = 120
70+
}
71+
6572
output "job_foo" {
6673
value = aap_job.sample_foo
6774
}

internal/provider/job_resource.go

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@ 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/int64default"
1619
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
1720
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1821
"github.com/hashicorp/terraform-plugin-framework/types"
22+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
1923
)
2024

2125
// Job AAP API model
@@ -31,14 +35,16 @@ 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+
WaitForCompletionTimeout types.Int64 `tfsdk:"wait_for_completion_timeout_seconds"`
4248
}
4349

4450
// JobResource is the resource implementation.
@@ -61,6 +67,40 @@ func NewJobResource() resource.Resource {
6167
return &JobResource{}
6268
}
6369

70+
// Given a string with the name of an AAP Job state, this function returns `true`
71+
// if such state is final and cannot transition further; a.k.a, the job is completed.
72+
func IsFinalStateAAPJob(state string) bool {
73+
finalStates := 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 := finalStates[state]
84+
return isPresent && result
85+
}
86+
87+
func retryUntilAAPJobReachesAnyFinalState(client ProviderHTTPClient, model JobResourceModel, diagnostics diag.Diagnostics) retry.RetryFunc {
88+
return func() *retry.RetryError {
89+
responseBody, err := client.Get(model.URL.ValueString())
90+
diagnostics.Append(model.ParseHttpResponse(responseBody)...)
91+
if err != nil {
92+
return retry.RetryableError(fmt.Errorf("error fetching job status: %s", err))
93+
}
94+
fmt.Printf("Job ID: %s, Current Status: %s\n", model.TemplateID, model.Status.ValueString())
95+
96+
if !IsFinalStateAAPJob(model.Status.ValueString()) {
97+
return retry.RetryableError(fmt.Errorf("job at: %s hasn't yet reached a final state. Current state: %s", model.URL, model.Status.ValueString()))
98+
} else {
99+
return nil
100+
}
101+
}
102+
}
103+
64104
// Metadata returns the resource type name.
65105
func (r *JobResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
66106
resp.TypeName = req.ProviderTypeName + "_job"
@@ -132,14 +172,32 @@ func (r *JobResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *
132172
Computed: true,
133173
Description: "The list of properties set by the user but ignored on server side.",
134174
},
175+
"wait_for_completion": schema.BoolAttribute{
176+
Optional: true,
177+
Computed: true,
178+
Default: booldefault.StaticBool(false),
179+
Description: "When this is set to `true`, Terraform will wait until this aap_job resource is created, reaches " +
180+
"any final status and then, proceeds with the following resource operation",
181+
},
182+
"wait_for_completion_timeout_seconds": schema.Int64Attribute{
183+
Optional: true,
184+
Computed: true,
185+
Default: int64default.StaticInt64(120),
186+
Description: "Sets the maximum amount of seconds Terraform will wait before timing out the updates, " +
187+
"and the job creation will fail. Default value of `120`",
188+
},
135189
},
136190
MarkdownDescription: "Launches an AAP job.\n\n" +
137191
"A job is launched only when the resource is first created or when the " +
138192
"resource is changed. The " + "`triggers`" + " argument can be used to " +
139193
"launch a new job based on any arbitrary value.\n\n" +
140194
"This resource always creates a new job in AAP. A destroy will not " +
141195
"delete a job created by this resource, it will only remove the resource " +
142-
"from the state.",
196+
"from the state.\n\n" +
197+
"Moreover, you can set `wait_for_completion` to true, then Terraform will " +
198+
"wait until this job is created and reaches any final state before continuing. " +
199+
"This parameter works in both create and update operations.\n\n" +
200+
"You can also tweak `wait_for_completion_timeout_seconds` to control the timeout limit.",
143201
}
144202
}
145203

@@ -157,6 +215,19 @@ func (r *JobResource) Create(ctx context.Context, req resource.CreateRequest, re
157215
return
158216
}
159217

218+
// If the job was configured to wait for completion, start polling the job status
219+
// and wait for it to complete before marking the resource as created
220+
if data.WaitForCompletion.ValueBool() {
221+
timeout := time.Duration(data.WaitForCompletionTimeout.ValueInt64()) * time.Second
222+
err := retry.RetryContext(ctx, timeout, retryUntilAAPJobReachesAnyFinalState(r.client, data, resp.Diagnostics))
223+
if err != nil {
224+
resp.Diagnostics.Append(diag.NewErrorDiagnostic("error when waiting for AAP job to complete", err.Error()))
225+
}
226+
if resp.Diagnostics.HasError() {
227+
return
228+
}
229+
}
230+
160231
// Save updated data into Terraform state
161232
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
162233
if resp.Diagnostics.HasError() {
@@ -210,6 +281,19 @@ func (r *JobResource) Update(ctx context.Context, req resource.UpdateRequest, re
210281
return
211282
}
212283

284+
// If the job was configured to wait for completion, start polling the job status
285+
// and wait for it to complete before marking the resource as created
286+
if data.WaitForCompletion.ValueBool() {
287+
timeout := time.Duration(data.WaitForCompletionTimeout.ValueInt64()) * time.Second
288+
err := retry.RetryContext(ctx, timeout, retryUntilAAPJobReachesAnyFinalState(r.client, data, resp.Diagnostics))
289+
if err != nil {
290+
resp.Diagnostics.Append(diag.NewErrorDiagnostic("error when waiting for AAP job to complete", err.Error()))
291+
}
292+
if resp.Diagnostics.HasError() {
293+
return
294+
}
295+
}
296+
213297
// Save updated data into Terraform state
214298
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
215299
if resp.Diagnostics.HasError() {

internal/provider/job_resource_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ 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+
WaitForCompletionTimeout: basetypes.NewInt64Value(60),
110+
},
111+
expected: []byte(`{"inventory":3}`),
112+
},
103113
}
104114

105115
for _, tc := range testTable {

templates/resources/job.md.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description: |-
88

99
{{ .Description | trimspace }}
1010

11-
-> **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.
11+
-> **Note** To pass an inventory to an aap_job resource, the underlying job template *must* have been configured to prompt for the inventory on launch.
1212

1313
{{ if .HasExample }}
1414
## Example Usage

0 commit comments

Comments
 (0)