How to get Ansible playbook to terminate When one task fails

Hello Team.
I have an Ansible playbook where am trying to automate VLAN mapping on my network.
Cureently, script is able to create and map VLAN on respective interfaces as set in the inventory. If one switch in the inventory has VLAN already, it aborts the process and goes to the next switch and if it does nit have then VLAN is created and mapped.
I need it to work in a manner that if VLAN exists in at least one switch, process should be aborted and playbook stopped.

/

  • name: Gather VLAN facts from Cisco devices
    hosts: cisco
    gather_facts: false

    vars_files:

    • /var/MIKROTIK/cisco/inventory

    vars_prompt:

    - name: “username”

    prompt: “Enter User Name”

    private: no

    - name: “password”

    prompt: “Enter your password”

    private: no

    • name: “VLAN”
      prompt: “Enter VLAN ID to add”
      private: no

    • name: “NAME”
      prompt: “Enter VLAN ID Name”
      private: no

    vars:

    - ansible_user:

    - ansible_password:

    tasks:

    • name: Check if VLAN exists
      nxos_command:
      commands:
      - show vlan id {{ VLAN }}
      register: vlan_output
      ignore_errors: true

    - name: Display message if VLAN exists on any switch

    fail:

    msg: “VLAN {{ VLAN }} already exists on {{ item }}. Aborting VLAN addition.”

    loop: “{{ ansible_play_batch }}”

    when: vlan_output.stdout is search(‘VLAN {{ VLAN }}’)

    • name: Fail if VLAN already exists
      block:

      • name: Set fact whether VLAN exists
        set_fact:
        vlan_exists: “{{ vlan_output.stdout | search(‘VLAN\s+’ + VLAN + ‘\s+’) is not none }}”
        delegate_to: localhost

      • name: Fail if VLAN exists
        fail:
        msg: “VLAN {{ VLAN }} already exists on {{ inventory_hostname }}. Aborting VLAN addition.”
        when: vlan_exists

      when: vlan_output is succeeded

    • name: Adding VLAN ID to Database
      nxos_vlans:
      config:
      - vlan_id: “{{ VLAN }}”
      name: “{{ NAME }}”
      state: active
      register: vlan_added
      when: vlan_output is failed

      when: vlan_output.stdout is not search(‘VLAN {{ VLAN }}’)

      when: “‘VLAN {{ VLAN }} not found in current VLAN database’ in vlan_output.stdout”

    • name: Send notification if VLAN already exists
      community.general.mattermost:
      text: “VLAN {{ VLAN }} already exists on {{ ansible_host }}. Aborting VLAN addition.”
      when: vlan_output is succeeded

    • name: Debug vlan_output variable
      debug:
      var: vlan_output

    • name: Debug etherports variable
      debug:
      var: etherports

    • name: Merge provided configuration with device configuration
      cisco.ios.ios_l2_interfaces:
      config:
      - name: “{{ item }}”
      mode: trunk
      trunk:
      allowed_vlans: “{{ VLAN }}”
      state: merged
      loop: “{{ etherports }}”

    • name: Send notification message via Mattermost if VLAN is added
      community.general.mattermost:

      text: |
      {% if vlan_added.changed %}
      VLAN {{ VLAN }} added successfully!
      Has been tagged to {{ ansible_host }} by User {{ ansible_user }} on the following interfaces:
      {{ etherports }}
      {% else %}
      VLAN {{ VLAN }} was not added as it already exist on host {{ ansible_host }}. Please use a new VLAN_ID, thank you!!
      {% endif %}

Could you use a code block for you post to make it more readable? For example as follows (terminating back ticks omitted as they generate an additional code block in this case):

```yaml
---
foo: bar

Sure, let me do that

1 Like
---
- name: Gather VLAN facts from Cisco devices
  hosts: cisco
  gather_facts: false
  
  vars_files:
    - /var/MIKROTIK/cisco/inventory

  vars_prompt:
    # - name: "username"
    #   prompt: "Enter User Name"
    #   private: no

    # - name: "password"
    #   prompt: "Enter your password"
    #   private: no
      
    - name: "VLAN"
      prompt: "Enter VLAN ID to add"
      private: no 

    - name: "NAME"
      prompt: "Enter VLAN ID Name"
      private: no    

  # vars:
  #   - ansible_user:
  #   - ansible_password: []

  

  tasks:
    - name: Check if VLAN exists
      nxos_command:
        commands: 
          - show vlan id {{ VLAN }}
      register: vlan_output
      ignore_errors: true

    # - name: Display message if VLAN exists on any switch
    #   fail:
    #     msg: "VLAN {{ VLAN }} already exists on {{ item }}. Aborting VLAN addition."
    #   loop: "{{ ansible_play_batch }}"
    #   when: vlan_output.stdout is search('VLAN {{ VLAN }}')
   

    - name: Fail if VLAN already exists
      block:
        - name: Set fact whether VLAN exists
          set_fact:
            vlan_exists: "{{ vlan_output.stdout | search('VLAN\\s+' + VLAN + '\\s+') is not none }}"
          delegate_to: localhost

        - name: Fail if VLAN exists
          fail:
            msg: "VLAN {{ VLAN }} already exists on {{ inventory_hostname }}. Aborting VLAN addition."
          when: vlan_exists

      when: vlan_output is succeeded
  

    - name: Adding VLAN ID to Database
      nxos_vlans:
        config:
          - vlan_id: "{{ VLAN }}"
            name: "{{ NAME }}"
            state: active
      register: vlan_added
      when: vlan_output is failed
      # when: vlan_output.stdout is not search('VLAN {{ VLAN }}')
      # when: "'VLAN {{ VLAN }} not found in current VLAN database' in vlan_output.stdout"

    - name: Send notification if VLAN already exists
      community.general.mattermost:
        text: "VLAN {{ VLAN }} already exists on {{ ansible_host }}. Aborting VLAN addition."
      when: vlan_output is succeeded

    - name: Debug vlan_output variable
      debug:
        var: vlan_output


    - name: Debug etherports variable
      debug:
        var: etherports
    
    - name: Merge provided configuration with device configuration
      cisco.ios.ios_l2_interfaces:
        config:
          - name: "{{ item }}"
            mode: trunk
            trunk:
              allowed_vlans: "{{ VLAN }}"
        state: merged
      loop: "{{ etherports }}"

  
    - name: Send notification message via Mattermost if VLAN is added
      community.general.mattermost:
        
        text: |
          {% if vlan_added.changed %}
          VLAN {{ VLAN }} added successfully!
          Has been tagged to {{ ansible_host }} by User {{ ansible_user }} on the following interfaces:
          {{ etherports }}
          {% else %}
          VLAN {{ VLAN }} was not added as it already exist on host {{ ansible_host }}. Please use a new VLAN_ID, thank you!!
          {% endif %}
          

    

Above is the ansible script am using

Anyone who is online to assist,

What you want is any_errors_fatal, see the docs Error handling in playbooks — Ansible Community Documentation coupled with an assert/fail on vlan existence.

2 Likes

You could also use the ansible.builtin.meta module, which allows you to end_play. This affects all inventory hosts in the play, so ansible will stop here.

I also think you might benefit from using block: rescue: and always: for handling what to do when your first command fails or succeeds. If the first command to check for VLAN succeeds on any inventory host, we want to abort the entire play correct? Then add meta: end_play in the block. If the play doesn’t end here, it continues to the rescue step.

---
- name: Gather VLAN facts from Cisco devices
  hosts: cisco
  gather_facts: false
  
  vars_files:
    - /var/MIKROTIK/cisco/inventory

  vars_prompt:
    # - name: "username"
    #   prompt: "Enter User Name"
    #   private: no

    # - name: "password"
    #   prompt: "Enter your password"
    #   private: no
      
    - name: "VLAN"
      prompt: "Enter VLAN ID to add"
      private: no 

    - name: "NAME"
      prompt: "Enter VLAN ID Name"
      private: no    

  # vars:
  #   - ansible_user:
  #   - ansible_password: []

  tasks:
    - name: Fail if VLAN already exists
      block:
        - name: Check if VLAN exists
          nxos_command:
            commands: 
              - show vlan id {{ VLAN }}
          register: vlan_output

        - name: Debug vlan_output variable
          debug:
            var: vlan_output

        - name: Debug etherports variable
          debug:
            var: etherports

        - name: Send notification if VLAN already exists
          community.general.mattermost:
            text: "VLAN {{ VLAN }} already exists on {{ ansible_host }}. Aborting VLAN addition."

        - name: End Play if VLAN exists
          meta: end_play

      rescue:
        - name: Debug vlan_output variable
          debug:
            var: vlan_output

        - name: Debug etherports variable
          debug:
            var: etherports

        - name: Adding VLAN ID to Database
          nxos_vlans:
            config:
              - vlan_id: "{{ VLAN }}"
                name: "{{ NAME }}"
                state: active
          register: vlan_added

        - name: Merge provided configuration with device configuration
          cisco.ios.ios_l2_interfaces:
            config:
              - name: "{{ item }}"
                mode: trunk
                trunk:
                  allowed_vlans: "{{ VLAN }}"
            state: merged
          loop: "{{ etherports }}"

        - name: Send notification message via Mattermost if VLAN is added
          community.general.mattermost:
            text: |
              {% if vlan_added.changed %}
              VLAN {{ VLAN }} added successfully!
              Has been tagged to {{ ansible_host }} by User {{ ansible_user }} on the following interfaces:
              {{ etherports }}
              {% else %}
              VLAN {{ VLAN }} was not added as it already exist on host {{ ansible_host }}. Please use a new VLAN_ID, thank you!!
              {% endif %}
2 Likes

Thanks @bcoca . Let me try it out

Getting bellow error
TASK [Set fact whether VLAN exists] ************************************************************************************************************************************************** fatal: [switch-01]: FAILED! => {"msg": "template error while templating string: Could not load \"search\": 'search'. String: {{ vlan_output.stdout | search('VLAN\\\\s+' + VLAN + '\\\\s+') is not none }}. Could not load \"search\": 'search'"}

@Denney-tech

TASK [Set fact whether VLAN exists] ************************************************************************************************************************************************** fatal: [switch-01]: FAILED! => {"msg": "template error while templating string: Could not load \"search\": 'search'. String: {{ vlan_output.stdout | search('VLAN\\\\s+' + VLAN + '\\\\s+') is not none }}. Could not load \"search\": 'search'"}

That looks like a typo, try replacing | search with | regex_search — see the ansible.builtin.regex_search filter documentation.

1 Like

Yep, typo in the sample code that was given. Didn’t catch that lol. Thanks @chris

1 Like

Thank you for the correction. But script only skipping a host where the VLAN is and proceeds to one that do not have. I need it to abort executing on another device if one of the host already has the VLAN`ansible-playbook add-vlan.yml -i inventory
Enter VLAN ID to add: 54
Enter VLAN ID Name: VID-54-TEST

PLAY [Gather VLAN facts from Cisco devices] ******************************************************************************************************************************************

TASK [Check if VLAN exists] **********************************************************************************************************************************************************
fatal: [switch-02]: FAILED! => {“changed”: false, “msg”: "show vlan id 54\r\r\nVLAN 54 not found in current VLAN database\r\n\r\n\r\n\rswitch# "}
ok: [switch-01]

TASK [Debug vlan_output variable] ****************************************************************************************************************************************************
ok: [switch-01] => {
“vlan_output”: {
“changed”: false,
“failed”: false,
“stdout”: [
“VLAN Name Status Ports\n---- -------------------------------- --------- -------------------------------\n54 VLAN0054 active \n\nVLAN Type Vlan-mode\n---- ----- ----------\n54 enet CE \n\nRemote SPAN VLAN\n----------------\nDisabled \n\nPrimary Secondary Type Ports\n------- --------- --------------- -------------------------------------------”
],
“stdout_lines”: [
[
“VLAN Name Status Ports”,
“---- -------------------------------- --------- -------------------------------”,
"54 VLAN0054 active ",
“”,
“VLAN Type Vlan-mode”,
“---- ----- ----------”,
"54 enet CE ",
“”,
“Remote SPAN VLAN”,
“----------------”,
"Disabled ",
“”,
“Primary Secondary Type Ports”,
“------- --------- --------------- -------------------------------------------”
]
]
}
}

TASK [Debug etherports variable] *****************************************************************************************************************************************************
ok: [switch-01] => {
“etherports”: [
“Ethernet1/20”,
“Ethernet1/15”
]
}

TASK [Set fact whether VLAN exists] **************************************************************************************************************************************************
ok: [switch-01]

TASK [Send notification if VLAN already exists] **************************************************************************************************************************************
ok: [switch-01]

TASK [End Play if VLAN exists] *******************************************************************************************************************************************************
skipping: [switch-01]

TASK [Debug vlan_output variable] ****************************************************************************************************************************************************
ok: [switch-02] => {
“vlan_output”: {
“changed”: false,
“failed”: true,
“msg”: "show vlan id 54\r\r\nVLAN 54 not found in current VLAN database\r\n\r\n\r\n\rswitch# "
}
}

TASK [Debug etherports variable] *****************************************************************************************************************************************************
ok: [switch-02] => {
“etherports”: [
“Ethernet1/24”,
“Ethernet1/25”
]
}

TASK [Adding VLAN ID to Database] ****************************************************************************************************************************************************
changed: [switch-02]

TASK [Merge provided configuration with device configuration] ************************************************************************************************************************
changed: [switch-02] => (item=Ethernet1/24)
changed: [switch-02] => (item=Ethernet1/25)

TASK [Send notification message via Mattermost if VLAN is added] *********************************************************************************************************************
ok: [switch-02]

PLAY RECAP ***************************************************************************************************************************************************************************
switch-01 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
switch-02 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0 `

Edited the snippet above. Not sure why the regex_search fails to find your vlan in the output, but we should already know it’s there when the first command succeeds right? I just removed the set_fact step and conditional on the end_play.

I think the issue with the regex_search was that successful output had ‘%04d’ padded the VLAN id: 54 VLAN0054 active which is "{{ VLAN }} VLAN{{ '%04d' | format(VLAN) }} active" in ansible.

So maybe try this if you want to continue double-checking the output.

        - name: Set fact whether VLAN exists
          vars:
            search: ".*{{ VLAN }} VLAN{{ '%04d' | format(VLAN) }} active.*"
          set_fact:
            vlan_exists: "{{ vlan_output.stdout | regex_search(search)}}"

Getting below error. Tried to figure out but no success


TASK [Set fact whether VLAN exists] *****************************************************************************************************************************************************************
fatal: [switch-01]: FAILED! => {"msg": "An unhandled exception occurred while templating '.*{{ VLAN }} VLAN{{ '%04d' | format(VLAN) }} active.*'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Unexpected templating type error occurred on (.*{{ VLAN }} VLAN{{ '%04d' | format(VLAN) }} active.*): %d format: a real number is required, not str. %d format: a real number is required, not str"}
fatal: [switch-02]: FAILED! => {"msg": "An unhandled exception occurred while templating '.*{{ VLAN }} VLAN{{ '%04d' | format(VLAN) }} active.*'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Unexpected templating type error occurred on (.*{{ VLAN }} VLAN{{ '%04d' | format(VLAN) }} active.*): %d format: a real number is required, not str. %d format: a real number is required, not str"}

I don’t know the answer but this is the error:

And this is the documentation for the format filter and that links to the printf-style String Formatting documentation.

@chris I think it’s treating his VLAN as a string since he’s entering it through prompt, not as a var.

So: search: ".*{{ VLAN }} VLAN{{ '%04d' | format(VLAN|int) }} active.*" should fix the typing.

That said, @Cysco_Colloh, it looks like VLAN0054 is the name field from the output? If that’s the case, it may be coincidence that it is “VLAN” + <0-padded-54>. Unless you can say with confidence that all of the names of your vlans are consistent (or even better, programmatic), then I can’t say my search string will be consistent.

On another note, while we have spent a lot of effort trying to help you achieve the process you’re going for, I can’t help but wonder if there’s an easier way to do all of this if you can change your approach.

Ansible is meant to be idempotent, where you can run the same playbook over and over again and never do the same thing twice. Obviously you want to do that with your vlans by making sure they’re unique to your switches. You could define the vlans in your host_vars for each switch, and then have the playbook only configure the vlans defined per switch. Depending on how many switches and vlans you have, that might be tedius, but at least then you would know the switches would get the vlans you want them to have. You could create the list of vlans you want first, gather facts about what vlans are there after they’re configured, and remove any extra vlans that weren’t in the original list (or at least notify you/network admins that there are ‘rogue’ vlans on a given switch).

2 Likes

Yes!!! indeed this line solved my problem. Now the script is behaving as per my expectations. Thanks a lot @Denney-tech @chris

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.