Error handling¶
Module failures are structured: alongside the human-readable msg, every mapped failure
carries an error_type your playbooks can branch on, and details with the underlying cause.
Nothing surfaces as a raw traceback.
- name: Approve, with explicit handling
netautomate.netorca.netorca_change_instance:
id: "{{ change_id }}"
state: APPROVED
register: result
failed_when: false
- name: Retry later only if the platform was down
ansible.builtin.set_fact:
retry_queue: "{{ retry_queue + [change_id] }}"
when: result.error_type | default('') == 'transient'
The taxonomy¶
error_type |
Trigger | Typical cause | The fix |
|---|---|---|---|
auth |
HTTP 401 | wrong/expired/truncated API key | re-issue and re-export NETORCA_API_KEY (authentication) |
permission |
HTTP 403 | the key's team does not own the target (or lacks rights); wrong context |
check whose key it is and the context parameter |
not_found |
HTTP 404 on a write target | the object was deleted or the ID is from the other context | verify the ID and context; info modules return empty instead of raising this |
transient |
HTTP 5xx, gateway errors, timeouts | platform restart, network blip | retry - see the pattern below |
api |
HTTP 400 | invalid filter/body value | the msg contains the server's validation payload verbatim - read it, it names the field |
parameter |
client-side | invalid value shape before any API call | fix the task |
| (argument spec) | client-side | unknown parameter or invalid choice | Ansible's own validation message lists the valid choices |
Recipes¶
401 immediately on any task¶
The key never worked: auth failures happen on the first real API call. Check for a truncated
paste (keys contain a dot: prefix.secret) and that the variable is exported in the environment
that runs ansible-playbook - not just in your shell profile.
First playbook shows the
exact output.
403 when approving or completing¶
NetOrca denied the request (HTTP 403): ... Check the API key's team permissions
and the 'context' parameter.
The key's team does not own the service whose change you are transitioning, or the task runs in
the wrong context. (Not to be confused with the service's allow_manual_approval /
allow_manual_completion flags - those govern GUI approvals by humans only; API transitions are
always available to the owning team.)
Illegal transition¶
Transition rules are enforced client-side with the legal targets listed
(the state machine). If you
hit this in a loop, your poll is racing another automation - poll the state you act from
(state: [PENDING] for approval, [APPROVED] for completion) immediately before acting.
Nothing matched - and that is not an error¶
Info modules return an empty list plus count: 0 when nothing matches, including id
lookups that do not exist. Assert emptiness explicitly where it matters:
- name: This VIP must not be claimed already
netautomate.netorca.netorca_service_item_info:
declaration:
virtual_server: {ip: 10.1.10.152, port: 80}
register: claimed
- ansible.builtin.assert:
that: claimed.count == 0
fail_msg: "already claimed by {{ claimed.service_items | map(attribute='name') | list }}"
Retrying transient failures¶
- name: Poll, riding out platform restarts
netautomate.netorca.netorca_change_instance_info:
state: [APPROVED]
register: work
retries: 3
delay: 30
until: work is not failed
Reserve until-retries for reads and polls. For writes, prefer failing the run and letting the
next scheduled run pick up the queue - the workflow is
re-runnable by design.
Reporting failure instead of swallowing it¶
In fulfilment automation, the correct reaction to your infrastructure failing is not
ignore_errors - it is telling NetOrca, so the consumer sees the truth:
block:
- name: Deploy the change
# ... your infrastructure tasks ...
- name: Report success
netautomate.netorca.netorca_change_instance:
id: "{{ change.id }}"
state: COMPLETED
rescue:
- name: Report failure with the reason
netautomate.netorca.netorca_change_instance:
id: "{{ change.id }}"
state: ERROR
log: "fulfilment failed: {{ ansible_failed_result.msg | default('unknown error') }}"
This block/rescue pair is the heart of the fulfilment scenario.
See the guardrails fire¶
guardrails_demo.yml triggers an illegal transition, an
invalid enum and a missing-ID lookup against a live instance - all safely - so you can read the
real failure output before you ever hit it in production.