Skip to content

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.