Skip to content

Commit 6659043

Browse files
authored
Improve error handling for async tasks (#70)
* Update gitignore * Add role to collect async errors * Update dispatch task to display errors * Update applications role to use new async handling * Update bulk_host_create role to use new async handling * Update credential_input_sources role to use new async handling * Update credential_types role to use new async handling * Update credentials role to use new async handling * Update execution_environments role to use new async handling * Update groups role to use new async handling * Update hosts role to use new async handling * Update instance_groups role to use new async handling * Update instances role to use new async handling * Update inventories role to use new async handling * Update inventory_sources role to use new async handling * Update job_templates role to use new async handling * Update inventory_source_update role to use new async handling * Update labels role to use new async handling * Update notification_templates role to use new async handling * Update organizations role to use new async handling * Update projects role to use new async handling * Update project_update role to use new async handling * Update roles role to use new async handling * Update schedules role to use new async handling * Update teams role to use new async handling * Update users role to use new async handling * Update workflow_job_templates role to use new async handling * Update readme to include new async handling details * Update metadata and add changelog fragment * Fix link * Fix pre-commit errors * Update settings role to use new async handling
1 parent 3fd1fc2 commit 6659043

File tree

62 files changed

+2416
-1697
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2416
-1697
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ tests/output
77
.vscode
88
ansible.cfg
99
.ansible/
10+
.devcontainer/
11+
.venv
12+
tests/localtest.yml

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,58 @@ Controller token module would be invoked with this code:
141141
142142
```
143143

144+
### Error Handling
145+
146+
Many of the roles in this collection use asynchrous tasks to perform their
147+
actions. By default the first failed asyncronous task will cause the playbook to
148+
fail. Setting the `controller_configuration_collect_logs` variable to `true`
149+
will enable collecting all asyncronous task failure messages and allow the
150+
playbook to run to completion.
151+
152+
When `controller_configuration_collect_logs` is enabled the reported errors are
153+
collected in a variable called `controller_configuration_role_errors`. This
154+
variable is a dictionary where each key is the type of the configuration item
155+
that failed to be applied. The value of each key is a list of all the failures
156+
of that type.
157+
158+
When the `dispatch` role is used and `controller_configuration_collect_logs` is
159+
enabled it will display any errors encountered while applying the configurations
160+
and fail.
161+
162+
Example Output when using the `dispatch` role and encoutering failures:
163+
164+
```yaml
165+
fatal: [localhost]: FAILED! => {
166+
"msg": [
167+
"Errors encountered applying configurations:",
168+
{
169+
"aap_organizations_errors": [
170+
{
171+
"ERROR_MESSAGE": "Request to /api/controller/v2/instance_groups/?name=not-real returned 0 items, expected 1",
172+
"name": "Test Organization"
173+
},
174+
{
175+
"ERROR_MESSAGE": "Request to /api/controller/v2/instance_groups/?name=not-real returned 0 items, expected 1",
176+
"name": "Test Organization 2"
177+
}
178+
],
179+
"controller_applications_errors": [
180+
{
181+
"ERROR_MESSAGE": "Request to /api/controller/v2/organizations/?name=UnknownOrg returned 0 items, expected 1",
182+
"name": "controller_application-failed-app1",
183+
"organization": "UnknownOrg"
184+
},
185+
{
186+
"ERROR_MESSAGE": "value of authorization_grant_type must be one of: password, authorization-code, got: password2",
187+
"name": "controller_application-failed-app2",
188+
"organization": "Default"
189+
}
190+
],
191+
}
192+
]
193+
}
194+
```
195+
144196
### Automate the Automation
145197

146198
Every Ansible Controller instance has it's own particularities and needs. Every administrator team has it's own practices and customs. This collection allows adaptation to every need, from small to large scale, having the objects distributed across multiple environments and leveraging Automation Webhook that can be used to link a Git repository and Ansible automation natively.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
minor_changes:
2+
- Add the ability to collect error logs while configuring the object, instead of failing on the first error.

galaxy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ authors:
1313
- Ivan Aragonés
1414
- Silvio Perez
1515
- Adonis García
16+
- Brant Evans @branic
1617
repository: https://github.com/redhat-cop/infra.controller_configuration/
1718
issues: https://github.com/redhat-cop/infra.controller_configuration/issues
1819
build_ignore:

roles/applications/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Currently:
2323
|`controller_password`|""|no|Controller Admin User's password on the Ansible Controller Server. This should be stored in an Ansible Vault at vars/controller-secrets.yml or elsewhere and called from a parent playbook. Either username / password or oauthtoken need to be specified.||
2424
|`controller_oauthtoken`|""|no|Controller Admin User's token on the Ansible Controller Server. This should be stored in an Ansible Vault at or elsewhere and called from a parent playbook. Either username / password or oauthtoken need to be specified.||
2525
|`controller_request_timeout`|`10`|no|Specify the timeout in seconds Ansible should use in requests to the controller host.||
26+
|`controller_configuration_collect_logs`|`false`|no|Specify whether to collect async results and continue for all failed async tasks instead of failing on the first error. Collected results are available in the `controller_configuration_role_errors` variable.||
2627
|`controller_applications`|`see below`|yes|Data structure describing your applications, described below. Alias: applications ||
2728

2829
### Enforcing defaults

roles/applications/tasks/main.yml

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,76 @@
11
---
22
# Create Controller applications
3-
- name: Managing Controller Applications
4-
application:
5-
name: "{{ __application_item.name | mandatory }}"
6-
new_name: "{{ __application_item.new_name | default(omit, true) }}"
7-
organization: "{{ __application_item.organization | mandatory }}"
8-
description: "{{ __application_item.description | default(('' if controller_configuration_applications_enforce_defaults else omit), true) }}"
9-
authorization_grant_type: "{{ __application_item.authorization_grant_type | default('password') }}"
10-
client_type: "{{ __application_item.client_type | default('public') }}"
11-
redirect_uris: "{{ __application_item.redirect_uris | default([]) }}"
12-
skip_authorization: "{{ __application_item.skip_authorization | default((false if controller_configuration_applications_enforce_defaults else omit), true) }}"
13-
state: "{{ __application_item.state | default(controller_state | default('present')) }}"
14-
15-
# Role specific options
16-
controller_username: "{{ controller_username | default(omit, true) }}"
17-
controller_password: "{{ controller_password | default(omit, true) }}"
18-
controller_oauthtoken: "{{ controller_oauthtoken | default(omit, true) }}"
19-
request_timeout: "{{ controller_request_timeout | default(omit, true) }}"
20-
controller_host: "{{ controller_hostname | default(omit, true) }}"
21-
controller_config_file: "{{ controller_config_file | default(omit, true) }}"
22-
validate_certs: "{{ controller_validate_certs | default(omit) }}"
23-
loop: "{{ applications if applications is defined else controller_applications }}"
24-
loop_control:
25-
loop_var: __application_item
26-
label: "{{ __operation.verb }} Controller Application {{ __application_item.name }}"
27-
pause: "{{ controller_configuration_applications_loop_delay }}"
28-
no_log: "{{ controller_configuration_applications_secure_logging }}"
29-
async: "{{ ansible_check_mode | ternary(0, 1000) }}"
30-
poll: 0
31-
register: __applications_job_async
32-
changed_when: (__applications_job_async.changed if ansible_check_mode else false)
3+
- name: Manage Controller Applications Block
334
vars:
34-
__operation: "{{ operation_translate[__application_item.state | default(controller_state) | default('present')] }}"
355
ansible_async_dir: "{{ controller_configuration_async_dir }}"
6+
no_log: "{{ controller_configuration_applications_secure_logging }}"
7+
block:
8+
- name: Managing Controller Applications
9+
application:
10+
name: "{{ __application_item.name | mandatory }}"
11+
new_name: "{{ __application_item.new_name | default(omit, true) }}"
12+
organization: "{{ __application_item.organization | mandatory }}"
13+
description: "{{ __application_item.description | default(('' if controller_configuration_applications_enforce_defaults else omit), true) }}"
14+
authorization_grant_type: "{{ __application_item.authorization_grant_type | default('password') }}"
15+
client_type: "{{ __application_item.client_type | default('public') }}"
16+
redirect_uris: "{{ __application_item.redirect_uris | default([]) }}"
17+
skip_authorization: "{{ __application_item.skip_authorization | default((false if controller_configuration_applications_enforce_defaults else omit), true) }}"
18+
state: "{{ __application_item.state | default(controller_state | default('present')) }}"
3619

37-
- name: Flag for errors (check mode only)
38-
ansible.builtin.set_fact:
39-
error_flag: true
40-
when: ansible_check_mode and __applications_job_async.failed is defined and __applications_job_async.failed
20+
# Role specific options
21+
controller_username: "{{ controller_username | default(omit, true) }}"
22+
controller_password: "{{ controller_password | default(omit, true) }}"
23+
controller_oauthtoken: "{{ controller_oauthtoken | default(omit, true) }}"
24+
request_timeout: "{{ controller_request_timeout | default(omit, true) }}"
25+
controller_host: "{{ controller_hostname | default(omit, true) }}"
26+
controller_config_file: "{{ controller_config_file | default(omit, true) }}"
27+
validate_certs: "{{ controller_validate_certs | default(omit) }}"
28+
loop: "{{ applications if applications is defined else controller_applications }}"
29+
loop_control:
30+
loop_var: __application_item
31+
label: "{{ __operation.verb }} Controller Application {{ __application_item.name }}"
32+
pause: "{{ controller_configuration_applications_loop_delay }}"
33+
async: "{{ ansible_check_mode | ternary(0, 1000) }}"
34+
poll: 0
35+
register: __applications_job_async
36+
changed_when: (__applications_job_async.changed if ansible_check_mode else false)
37+
vars:
38+
__operation: "{{ operation_translate[__application_item.state | default(controller_state) | default('present')] }}"
4139

42-
- name: Managing Controller Applications | Wait for finish the Application management
43-
ansible.builtin.async_status:
44-
jid: "{{ __applications_job_async_results_item.ansible_job_id }}"
45-
register: __applications_job_async_result
46-
until: __applications_job_async_result.finished
47-
retries: "{{ controller_configuration_applications_async_retries }}"
48-
delay: "{{ controller_configuration_applications_async_delay }}"
49-
loop: "{{ __applications_job_async.results }}"
50-
loop_control:
51-
loop_var: __applications_job_async_results_item
52-
label: "{{ __operation.verb }} Controller Application {{ __applications_job_async_results_item.__application_item.name }} | Wait for finish the Application {{
53-
__operation.action }}"
54-
when: not ansible_check_mode and __applications_job_async_results_item.ansible_job_id is defined
55-
no_log: "{{ controller_configuration_applications_secure_logging }}"
56-
vars:
57-
__operation: "{{ operation_translate[__applications_job_async_results_item.__application_item.state | default(controller_state) | default('present')] }}"
58-
ansible_async_dir: "{{ controller_configuration_async_dir }}"
40+
- name: Flag for errors (check mode only)
41+
ansible.builtin.set_fact:
42+
error_flag: true
43+
when: ansible_check_mode and __applications_job_async.failed is defined and __applications_job_async.failed
44+
45+
- name: Managing Controller Applications | Wait for finish the Application management
46+
when:
47+
- not ansible_check_mode
48+
- __applications_job_async_results_item.ansible_job_id is defined
49+
ansible.builtin.include_role:
50+
name: infra.controller_configuration.collect_async_status
51+
loop: "{{ __applications_job_async.results }}"
52+
loop_control:
53+
loop_var: __applications_job_async_results_item
54+
label: "{{ __operation.verb }} Controller Application {{ __applications_job_async_results_item.__application_item.name }} | Wait for finish the Application {{
55+
__operation.action }}"
56+
vars:
57+
cas_secure_logging: "{{ controller_configuration_applications_secure_logging }}"
58+
cas_async_delay: "{{ controller_configuration_applications_async_delay }}"
59+
cas_async_retries: "{{ controller_configuration_applications_async_retries }}"
60+
cas_job_async_results_item: "{{ __applications_job_async_results_item }}"
61+
cas_error_list_var_name: "controller_applications_errors"
62+
__operation: "{{ operation_translate[__applications_job_async_results_item.__application_item.state | default(controller_state) | default('present')] }}"
63+
64+
always:
65+
- name: Cleanup async results files
66+
when:
67+
- not ansible_check_mode
68+
- __applications_job_async_results_item.ansible_job_id is defined
69+
ansible.builtin.async_status:
70+
jid: "{{ __applications_job_async_results_item.ansible_job_id }}"
71+
mode: cleanup
72+
loop: "{{ __applications_job_async.results }}"
73+
loop_control:
74+
loop_var: __applications_job_async_results_item
75+
label: "Cleaning up job results file: {{ __applications_job_async_results_item.results_file | default('Unknown') }}"
5976
...

roles/bulk_host_create/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Currently:
2222
|`controller_password`|""|no|Controller Admin User's password on the Ansible Controller Server. This should be stored in an Ansible Vault at vars/controller-secrets.yml or elsewhere and called from a parent playbook. Either username / password or oauthtoken need to be specified.||
2323
|`controller_oauthtoken`|""|no|Controller Admin User's token on the Ansible Controller Server. This should be stored in an Ansible Vault at or elsewhere and called from a parent playbook. Either username / password or oauthtoken need to be specified.||
2424
|`controller_request_timeout`|`10`|no|Specify the timeout in seconds Ansible should use in requests to the controller host.||
25+
|`controller_configuration_collect_logs`|`false`|no|Specify whether to collect async results and continue for all failed async tasks instead of failing on the first error. Collected results are available in the `controller_configuration_role_errors` variable.||
2526
|`controller_configuration_bulk_hosts_secure_logging`|`see below`|yes|Data structure describing your organization or organizations Described below.||
2627

2728
### Secure Logging Variables
Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,63 @@
11
---
22
# Create Job Template
3-
- name: Add Controller hosts in bulk
4-
bulk_host_create:
5-
hosts: "{{ __controller_bulk_hosts_item.hosts }}"
6-
inventory: "{{ __controller_bulk_hosts_item.inventory }}"
7-
8-
# Role Standard options
9-
controller_username: "{{ controller_username | default(omit, true) }}"
10-
controller_password: "{{ controller_password | default(omit, true) }}"
11-
controller_oauthtoken: "{{ controller_oauthtoken | default(omit, true) }}"
12-
request_timeout: "{{ controller_request_timeout | default(omit, true) }}"
13-
controller_host: "{{ controller_hostname | default(omit, true) }}"
14-
controller_config_file: "{{ controller_config_file | default(omit, true) }}"
15-
validate_certs: "{{ controller_validate_certs | default(omit) }}"
16-
loop: "{{ controller_bulk_hosts }}"
17-
loop_control:
18-
loop_var: __controller_bulk_hosts_item
19-
pause: "{{ controller_configuration_bulk_hosts_loop_delay }}"
20-
no_log: "{{ controller_configuration_bulk_hosts_secure_logging }}"
21-
async: "{{ ansible_check_mode | ternary(0, 1000) }}"
22-
poll: 0
23-
register: __controller_bulk_hosts_job_async
24-
changed_when: (__controller_bulk_hosts_job_async.changed if ansible_check_mode else false)
3+
- name: Manage Controller Applications Block
254
vars:
265
ansible_async_dir: "{{ controller_configuration_async_dir }}"
6+
no_log: "{{ controller_configuration_bulk_hosts_secure_logging }}"
7+
block:
8+
- name: Add Controller hosts in bulk
9+
bulk_host_create:
10+
hosts: "{{ __controller_bulk_hosts_item.hosts }}"
11+
inventory: "{{ __controller_bulk_hosts_item.inventory }}"
2712

28-
- name: Flag for errors (check mode only)
29-
ansible.builtin.set_fact:
30-
error_flag: true
31-
when: ansible_check_mode and __controller_bulk_hosts_job_async.failed is defined and __controller_bulk_hosts_job_async.failed
13+
# Role Standard options
14+
controller_username: "{{ controller_username | default(omit, true) }}"
15+
controller_password: "{{ controller_password | default(omit, true) }}"
16+
controller_oauthtoken: "{{ controller_oauthtoken | default(omit, true) }}"
17+
request_timeout: "{{ controller_request_timeout | default(omit, true) }}"
18+
controller_host: "{{ controller_hostname | default(omit, true) }}"
19+
controller_config_file: "{{ controller_config_file | default(omit, true) }}"
20+
validate_certs: "{{ controller_validate_certs | default(omit) }}"
21+
loop: "{{ controller_bulk_hosts }}"
22+
loop_control:
23+
loop_var: __controller_bulk_hosts_item
24+
pause: "{{ controller_configuration_bulk_hosts_loop_delay }}"
25+
async: "{{ ansible_check_mode | ternary(0, 1000) }}"
26+
poll: 0
27+
register: __controller_bulk_hosts_job_async
28+
changed_when: (__controller_bulk_hosts_job_async.changed if ansible_check_mode else false)
3229

33-
- name: Configure bulk_hosts | Wait for finish the bulk_hosts creation
34-
ansible.builtin.async_status:
35-
jid: "{{ __controller_bulk_hosts_job_async_results_item.ansible_job_id }}"
36-
register: __controller_bulk_hosts_job_async_result
37-
until: __controller_bulk_hosts_job_async_result.finished
38-
retries: "{{ controller_configuration_bulk_hosts_async_retries }}"
39-
delay: "{{ controller_configuration_bulk_hosts_async_delay }}"
40-
loop: "{{ __controller_bulk_hosts_job_async.results }}"
41-
loop_control:
42-
loop_var: __controller_bulk_hosts_job_async_results_item
43-
when: not ansible_check_mode and __controller_bulk_hosts_job_async_results_item.ansible_job_id is defined
44-
no_log: "{{ controller_configuration_bulk_hosts_secure_logging }}"
45-
vars:
46-
ansible_async_dir: "{{ controller_configuration_async_dir }}"
30+
- name: Flag for errors (check mode only)
31+
ansible.builtin.set_fact:
32+
error_flag: true
33+
when: ansible_check_mode and __controller_bulk_hosts_job_async.failed is defined and __controller_bulk_hosts_job_async.failed
34+
35+
- name: Configure bulk_hosts | Wait for finish the bulk_hosts creation
36+
when:
37+
- not ansible_check_mode
38+
- __controller_bulk_hosts_job_async_results_item.ansible_job_id is defined
39+
ansible.builtin.include_role:
40+
name: infra.controller_configuration.collect_async_status
41+
loop: "{{ __controller_bulk_hosts_job_async.results }}"
42+
loop_control:
43+
loop_var: __controller_bulk_hosts_job_async_results_item
44+
vars:
45+
cas_secure_logging: "{{ controller_configuration_bulk_hosts_secure_logging }}"
46+
cas_async_delay: "{{ controller_configuration_bulk_hosts_async_delay }}"
47+
cas_async_retries: "{{ controller_configuration_bulk_hosts_async_retries }}"
48+
cas_job_async_results_item: "{{ __controller_bulk_hosts_job_async_results_item }}"
49+
cas_error_list_var_name: "controller_bulk_hosts_errors"
50+
51+
always:
52+
- name: Cleanup async results files
53+
when:
54+
- not ansible_check_mode
55+
- __controller_bulk_hosts_job_async_results_item.ansible_job_id is defined
56+
ansible.builtin.async_status:
57+
jid: "{{ __controller_bulk_hosts_job_async_results_item.ansible_job_id }}"
58+
mode: cleanup
59+
loop: "{{ __controller_bulk_hosts_job_async.results }}"
60+
loop_control:
61+
loop_var: __controller_bulk_hosts_job_async_results_item
62+
label: "Cleaning up job results file: {{ __controller_bulk_hosts_job_async_results_item.results_file | default('Unknown') }}"
4763
...

0 commit comments

Comments
 (0)