{"id":3497,"date":"2020-02-06T16:50:19","date_gmt":"2020-02-06T15:50:19","guid":{"rendered":"http:\/\/miro.borodziuk.eu\/?p=3497"},"modified":"2020-05-01T18:20:43","modified_gmt":"2020-05-01T16:20:43","slug":"ansible-handling-errors-2","status":"publish","type":"post","link":"http:\/\/miro.borodziuk.eu\/index.php\/2020\/02\/06\/ansible-handling-errors-2\/","title":{"rendered":"Ansible Handling Errors"},"content":{"rendered":"<p>Ansible evaluates the return codes of modules and commands to determine whether a task succeeded or failed. Normally, when a task fails Ansible immediately aborts the plays, skipping all subsequent tasks.<br \/>\nHowever, sometimes an administrator may want to have playbook execution continue even if a task fails. For example, it may be expected that a particular command will fail.<\/p>\n<p><!--more--><\/p>\n<p>There are a number of Ansible features that may be used to better manage task errors:<\/p>\n<ul>\n<li><strong>Ignore the failed task<\/strong>: By default, if a task fails, the play is aborted; however, this behavior can be overridden by skipping failed tasks. To do so, the <code><strong>ignore_errors <\/strong><\/code>keyword needs to be used in a task. The following snippet shows how to use <code><strong>ignore_errors <\/strong><\/code>to continue playbook execution even if the task fails. For example, if the <em>notapkg<\/em> package does not exist the <em>yum<\/em><br \/>\nmodule will fail, but having <code><strong>ignore_errors <\/strong><\/code>set to yes will allow execution to continue.<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">- yum:\r\n    name: notapkg\r\n    state: latest\r\n  ignore_errors: yes<\/pre>\n<ul>\n<li><strong>Force execution of handlers<\/strong>: By default, if a task that notifies a handler fails, the handler will be skipped as well. Administrators can override this behavior by using the <code><strong>force_handlers <\/strong><\/code>keyword in task. This forces the handler to be called even if the task fails. The following snippet shows hows to use the <code><strong>force_handlers<\/strong><\/code> keyword in a task to forcefully execute the handler even if the task fails:<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: all\r\n  force_handlers: yes\r\n  tasks:\r\n    - yum:\r\n        name: notapkg\r\n        state: latest\r\n      notify: restart_database\r\n\r\n  handlers:\r\n    - name: restart_database\r\n      service:\r\n        name: mariadb\r\n        state: restarted<\/pre>\n<ul>\n<li><strong>Override the failed state<\/strong>: A task itself can succeed, yet administrators may want to mark the task as failed based on specific criteria. To do so, the <code><strong>failed_when<\/strong><\/code> keyword can be used with a task. This is often used with modules that execute remote commands and capture the output in a variable. For example, administrators can run a script that outputs an error message and use that message to define the failed state for the task. The following snippet shows how the <code><strong>failed_when<\/strong><\/code> keyword can be used in a task:<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">tasks:\r\n  - shell:\r\n      cmd: \/usr\/local\/bin\/create_users.sh\r\n      register: command_result\r\n    failed_when: \"'Password missing' in command_result.stdout\"<\/pre>\n<ul>\n<li><strong>Override the changed state<\/strong>: When a task updates a managed host, it acquires the changed state. However, if a task does not perform any change on the managed node, handlers are skipped. The <code><strong>changed_when<\/strong> <\/code>keyword can be used to override the default behavior of what triggers the changed state. For example, if administrators want to restart a service every time a playbook runs, the <code><strong>changed_when<\/strong> <\/code>keyword can be added to the task. The following snippet shows how a handler can be triggered every time by forcing the changed state:<\/li>\n<\/ul>\n<pre class=\"lang:sh decode:true\">tasks:\r\n  - shell:\r\n      cmd: \/usr\/local\/bin\/upgrade-database\r\n    register: command_result\r\n    changed_when: \"'Success' in command_result.stdout\"\r\n    notify:\r\n      - restart_database\r\n\r\nhandlers:\r\n  - name: restart_database\r\n    service:\r\n      name: mariadb\r\n      state: restarted<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Ansible blocks and error handling<\/span><br \/>\nIn playbooks, blocks are clauses that enclose tasks. Blocks allow for logical grouping of tasks, and can be used to control how tasks are executed. For example, administrators can define a main set of tasks and a set of extra tasks that will only be executed if the first set fails. To do so, blocks in playbooks can be used with three keywords:<\/p>\n<ul>\n<li><strong>block<\/strong>: Defines the main tasks to run.<\/li>\n<li><strong>rescue<\/strong>: Defines the tasks that will be run if the tasks defined in the block clause fails.<\/li>\n<li><strong>always<\/strong>: Defines the tasks that will always run independently of the success or failure of tasks defined in the block and rescue clauses.<\/li>\n<\/ul>\n<p>The following example shows how to implement a block in a playbook. Even if tasks defined in the block clause fail, tasks defined in the rescue and always clause will be executed:<\/p>\n<pre class=\"lang:sh decode:true\">tasks:\r\n  - block:\r\n      - shell:\r\n          cmd: \/usr\/local\/lib\/upgrade-database\r\n    \r\n    rescue:\r\n      - shell:\r\n          cmd: \/usr\/local\/lib\/create-users\r\n \r\n    always:\r\n      - service:\r\n          name: mariadb\r\n          state: restarted<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 1. Configure Error Handling<\/span><\/p>\n<p>The playbook below will copy url files from managedhosts.<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ cat playbook5.yml\r\n---\r\n- hosts: localhost\r\n  tasks:\r\n  - name: get files\r\n    get_url:\r\n      url: \"http:\/\/{{item}}\/index.html\"\r\n      dest: \"\/tmp\/{{item}}\"\r\n    ignore_errors: yes\r\n    with_items:\r\n      - managedhost1\r\n      - managedhost2<\/pre>\n<p>Ensuring that http service is at started state at both servers.<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible labservers -b -m service -a \"name=httpd state=started\"<\/pre>\n<p>Now we are running playbook5:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ ansible-playbook playbook5.yml\r\n\r\nPLAY [localhost] *****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [get files] *****************************************************************************************************************\r\nok: [localhost] =&gt; (item=managedhost1)\r\nok: [localhost] =&gt; (item=managedhost2)\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nlocalhost : ok=2 changed=0 unreachable=0 failed=0\r\n\r\n[miro@controlnode playbooks]$ cat \/tmp\/managedhost1\r\nHello World !\r\n[miro@controlnode playbooks]$ cat \/tmp\/managedhost2\r\nHello World !\r\nI'm back!!<\/pre>\n<p>Ass you can see <code>index.html<\/code> is copied into <code>\/tmp<\/code> directory<\/p>\n<p>Let&#8217;s stop <em>http<\/em> service at <em>managedhost1<\/em><\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible managedhost1 -b -m service -a \"name=httpd state=stopped\"<\/pre>\n<p>And remove i<code>ndex.html<\/code> files from <code>\/tmp<\/code><\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ rm \/tmp\/managedhost*<\/pre>\n<p>When we run playbook5 now we will see errors:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible-playbook playbook5.yml\r\n\r\nPLAY [localhost] *****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [get files] *****************************************************************************************************************\r\nfailed: [localhost] (item=managedhost1) =&gt; {\"changed\": false, \"dest\": \"\/tmp\/managedhost1\", \"item\": \"managedhost1\", \"msg\": \"Request failed: &lt;urlopen error [Errno 111] Po\u0142\u0105czenie odrzucone&gt;\", \"state\": \"absent\", \"url\": \"http:\/\/managedhost1\/index.html\"}\r\nchanged: [localhost] =&gt; (item=managedhost2)\r\n...ignoring\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nlocalhost : ok=2 changed=1 unreachable=0 failed=0<\/pre>\n<p>Now we will write a playbook with error debugging::<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ cat playbook6.yml\r\n---\r\n- hosts: localhost\r\n  tasks:\r\n  - name: get files\r\n      block:\r\n        - get_url:\r\n            url: \"http:\/\/managedhost2\/index.html\"\r\n            dest: \"\/tmp\/index_file\"\r\n      rescue:\r\n        - debug: msg=\"The file doesn't exists!\"\r\n      always:\r\n        - debug: msg=\"Play done !\"<\/pre>\n<p>Running the playbook:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ ansible-playbook playbook6.yml\r\n\r\nPLAY [localhost] *****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [get_url] *******************************************************************************************************************\r\nchanged: [localhost]\r\n\r\nTASK [debug] *********************************************************************************************************************\r\nok: [localhost] =&gt; {\r\n\"msg\": \"Play done !\"\r\n}\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nlocalhost : ok=3 changed=1 unreachable=0 failed=0<\/pre>\n<p>Everything went well:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode playbooks]$ cat \/tmp\/index_file\r\nHello World !\r\nI'm back!!<\/pre>\n<p>Let&#8217;s stop http service at managedhost2:<\/p>\n<pre class=\"lang:sh decode:true \">[miro@controlnode etc]$ ansible managedhost2.example.com -b -m service -a \"name=httpd state=stopped\"<\/pre>\n<p>And we can see the message about problems:<\/p>\n<pre class=\"lang:sh decode:true\">[miro@controlnode playbooks]$ ansible-playbook playbook6.yml\r\n\r\nPLAY [localhost] *****************************************************************************************************************\r\n\r\nTASK [Gathering Facts] ***********************************************************************************************************\r\nok: [localhost]\r\n\r\nTASK [get_url] *******************************************************************************************************************\r\nfatal: [localhost]: FAILED! =&gt; {\"changed\": false, \"dest\": \"\/tmp\/index_file\", \"msg\": \"Request failed: &lt;urlopen error [Errno 111] Po\u0142\u0105czenie odrzucone&gt;\", \"state\": \"absent\", \"url\": \"http:\/\/managedhost2\/index.html\"}\r\n\r\nTASK [debug] *********************************************************************************************************************\r\nok: [localhost] =&gt; {\r\n\"msg\": \"The file doesn't exists!\"\r\n}\r\n\r\nTASK [debug] *********************************************************************************************************************\r\nok: [localhost] =&gt; {\r\n\"msg\": \"Play done !\"\r\n}\r\n\r\nPLAY RECAP ***********************************************************************************************************************\r\nlocalhost : ok=3 changed=0 unreachable=0 failed=1\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 2. Handling errors<\/span><\/p>\n<p>Playbook <code>playbook.yml<\/code> targets all hosts in the mailservers group. It initializes four variables: <em>maildir_path<\/em> with a value of <code>\/home\/john\/Maildir<\/code>, <em>maildir<\/em> with a value of <code>\/home\/student\/Maildir<\/code>, <em>mail_package<\/em> with a value of <em>postfix<\/em>, and <em>mail_service<\/em> with a value of <em>postfix<\/em>. It also has a tasks section that defines a task that uses the <em>copy<\/em> module to install a directory to the managed host. It has a second task that installs the package that the <em>mail_package<\/em> variable defines.<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: mailservers\r\n  vars:\r\n    maildir_path: \/home\/john\/Maildir\r\n    maildir: \/home\/student\/Maildir\r\n    mail_package: postfix\r\n    mail_service: postfix\r\n\r\n  tasks:\r\n    - name: Create {{ maildir_path }}\r\n        copy:\r\n          src: \"{{ maildir }}\"\r\n          dest: \"{{ maildir_path }}\"\r\n          mode: 0755\r\n\r\n    - name: Install {{ mail_package }} package\r\n        yum:\r\n          name: \"{{ mail_package }}\"\r\n          state: latest<\/pre>\n<p>Run the playbook. Watch as the first task fails, resulting in the failure of the play.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation demo-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Create \/home\/john\/Maildir] ***********************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": false, \"failed\": true,\r\n\"msg\": \"could not find src=\/home\/student\/Maildir\"}\r\nNO MORE HOSTS LEFT *************************************************************\r\nto retry, use: --limit @playbook.retry\r\n... Output omitted ...<\/pre>\n<p>Update the first task by adding the <code>ignore_errors<\/code> keyword with a value of yes. The updated task should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">- name: Create {{ maildir_path }}\r\n    copy:\r\n      src: \"{{ maildir }}\"\r\n      dest: \"{{ maildir_path }}\"\r\n      mode: 0755\r\n   ignore_errors: yes<\/pre>\n<p>Run the playbook and watch as the second task runs, despite the fact that the first task failed.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation demo-failures]$ ansible-playbook.playbook.yml\r\n... Output omitted ...\r\nTASK [Create \/home\/john\/Maildir] ***********************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": false, \"failed\": true,\r\n\"msg\": \"could not find src=\/home\/student\/Maildir\"}\r\n...ignoring\r\nTASK [Install postfix package] *************************************************\r\nchanged: [servera.lab.example.com]\r\n.... Output omitted ...<\/pre>\n<p>Create a block in the tasks section and nest the tasks into it. Remove the line <code>ignore_errors: yes<\/code> for the first task.<\/p>\n<p>Move the task that installs the postfix package into the rescue clause. Update the task to also install the dovecot package.<\/p>\n<p>Add an always clause. It should create a task to start both the postfix and dovecot services. Register the output of the command in the command_result variable.<\/p>\n<p>Add a debug task outside the block that outputs the command_result variable.<\/p>\n<p>The playbook should now read as follows:<\/p>\n<pre class=\"lang:sh decode:true \">---\r\n- hosts: mailservers\r\n    vars:\r\n      maildir_path: \/home\/john\/Maildir\r\n      maildir: \/home\/student\/Maildir\r\n      mail_package: postfix\r\n      mail_service: postfix\r\n   \r\n    tasks:\r\n      - block:\r\n          - name: Create {{ maildir_path }}\r\n              copy:\r\n                src: \"{{ maildir }}\"\r\n                dest: \"{{ maildir_path }}\"\r\n                mode: 0755\r\n        \r\n        rescue:\r\n          - name: Install mail packages\r\n              yum:\r\n                name: \"{{ item }}\"\r\n                state: latest\r\n              with_items:\r\n                - \"{{ mail_package }}\"\r\n                - dovecot\r\n     \r\n        always:\r\n          - name: Start mail services\r\n              service:\r\n                name: \"{{ item }}\"\r\n                state: started\r\n              with_items:\r\n                - \"{{ mail_service }}\"\r\n                - dovecot\r\n              register: command_result\r\n\r\n      - debug:\r\n           var: command_result<\/pre>\n<p>Run the playbook, and watch as the rescue and always clauses run despite the fact that the first task failed.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation demo-failures]$ ansible-playbook.playbook.yml\r\n... Output omitted ...\r\nTASK [Create \/home\/john\/Maildir] ***********************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": false,\r\n\"failed\": true, \"msg\": \"could not find src=\/home\/student\/Maildir\"}\r\nTASK [Install mail packages] ***************************************************\r\nchanged: [servera.lab.example.com] =&gt; (item=[u'postfix', u'dovecot'])\r\nTASK [Start mail services] *****************************************************\r\nchanged: [servera.lab.example.com] =&gt; (item=postfix)\r\nchanged: [servera.lab.example.com] =&gt; (item=dovecot)\r\nTASK [debug] *******************************************************************\r\nok: [servera.lab.example.com] =&gt; {\r\n\"command_result\": {\r\n\"changed\": true,\r\n\"msg\": \"All items completed\",\r\n\"results\": [\r\n{\r\n... Output omitted ...<\/pre>\n<p>To restart the two services every time, the<code> changed_when<\/code> keyword can be used. To do so, capture the output of the first task into a variable; the variable will be used to force the state of the task that installs the packages. A handlers section will be added to the playbook.<\/p>\n<p>Update the task in the block section by saving the output of the command into the command_output variable.<\/p>\n<p>Update the task in the rescue section by adding a changed_when condition as well as a notify section for the restart_dovecot handler. The condition will read the command_output variable to forcefully mark the task as changed.<\/p>\n<p>Update the task in the always section to restart the service defined by the mail_service variable.<\/p>\n<p>Append a handlers section to restart the dovecot service.<\/p>\n<p>When updated, the tasks and handlers sections should read as follows:<\/p>\n<pre class=\"lang:sh decode:true \">tasks:\r\n  - block:\r\n      - name: Create {{ maildir_path }}\r\n          copy:\r\n            src: \"{{ maildir }}\"\r\n            dest: \"{{ maildir_path }}\"\r\n            mode: 0755\r\n          register: command_output\r\n   \r\n    rescue:\r\n      - name: Install mail packages\r\n          yum:\r\n            name: \"{{ item }}\"\r\n            state: latest\r\n          with_items:\r\n            - \"{{ mail_package }}\"\r\n            - dovecot\r\n          changed_when: \"'not find' in command_output.msg\"\r\n          notify: restart_dovecot\r\n\r\n    always:\r\n      - name: Start mail services\r\n          service:\r\n            name: \"{{ mail_service }}\"\r\n            state: restarted\r\n          register: command_result\r\n\r\n  - debug:\r\n      var: command_result\r\n\r\nhandlers:\r\n  - name: restart_dovecot\r\n      service:\r\n        name: dovecot\r\n        state: restarted<\/pre>\n<p>Run the playbook. Watch as the handler is called, despite the fact that the <em>dovecot<\/em> package is already installed:<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation demo-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Install mail packages] ***************************************************\r\nchanged: [servera.lab.example.com] =&gt; (item=[u'postfix', u'dovecot'])\r\n... Output omitted ...\r\nRUNNING HANDLER [restart_dovecot] **********************************************\r\nchanged: [servera.lab.example.com]\r\n... Output omitted ...<\/pre>\n<p>&nbsp;<\/p>\n<p><!--EndFragment--><\/p>\n<p><span style=\"color: #3366ff;\">Example 3.<\/span><\/p>\n<p>Create the<code> playbook.yml<\/code> playbook. Initialize three variables: <em>web_package<\/em> with a value of <em>http<\/em>, <em>db_package<\/em> with a value of <em>mariadb-server<\/em> and <em>db_service<\/em> with a value of <em>mariadb<\/em>. The variables will be used to install the required packages and start the server. The <em>http<\/em> value is an intentional error in the package name. Define two tasks that use the <em>yum<\/em> module and the two variables, <em>web_package<\/em> and <em>db_package<\/em>. The task will install the required packages. The file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: databases\r\n    vars:\r\n      web_package: http\r\n      db_package: mariadb-server\r\n      db_service: mariadb\r\n\r\n  tasks:\r\n    - name: Install {{ web_package }} package\r\n        yum:\r\n          name: \"{{ web_package }}\"\r\n          state: latest\r\n    \r\n    - name: Install {{ db_package }} package\r\n        yum:\r\n          name: \"{{ db_package }}\"\r\n          state: latest<\/pre>\n<p>Run the playbook and watch the output of the play.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Install http package] ****************************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": false, \"failed\": true,\r\n\"msg\": \"No Package matching 'http' found available, installed or updated\",\r\n\"rc\": 0, \"results\": []}\r\n... Output omitted ...<\/pre>\n<p>The task failed because there is no existing package called <em>http<\/em>. Because the first task failed, the second task was skipped. Update the first task to ignore any errors by adding the <code>ignore_errors<\/code> keyword. The file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: databases\r\n    vars:\r\n      web_package: http\r\n      db_package: mariadb-server\r\n      db_service: mariadb\r\n\r\n  tasks:\r\n    - name: Install {{ web_package }} package\r\n        yum:\r\n          name: \"{{ web_package }}\"\r\n          state: latest\r\n        ignore_errors: yes\r\n    \r\n    - name: Install {{ db_package }} package\r\n        yum:\r\n          name: \"{{ db_package }}\"\r\n          state: latest<\/pre>\n<p>Run the playbook another time and watch the output of the play.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Install http package] ****************************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": false, \"failed\": true,\r\n\"msg\": \"No Package matching 'http' found available, installed or updated\",\r\n\"rc\": 0, \"results\": []}\r\n...ignoring\r\nTASK [Install mariadb-server package] ******************************************\r\nok: [servera.lab.example.com]\r\n... Output omitted ...<\/pre>\n<p>Despite the fact that the first task failed, Ansible executed the second one.<\/p>\n<p>Insert a new task at the beginning of the playbook. The task will execute a remote command and capture the output. The output of the command is used by the task that installs the mariadb-server package to override what Ansible considers as a failure.<\/p>\n<p>In the playbook, insert a task at the beginning of the playbook that executes a remote command and saves the output in the command_result variable. The play should also continue if the task fails; to do so, add the<code> ignore_errors<\/code> keyword. The playbook should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: databases\r\n    vars:\r\n      web_package: http\r\n      db_package: mariadb-server\r\n      db_service: mariadb\r\n\r\n  tasks:\r\n    - name: Check {{ web_package }} installation status \r\n        command: yum list installed \"{{ web_package }}\" \r\n      register: command_result \r\n      ignore_errors: yes\r\n\r\n    - name: Install {{ web_package }} package\r\n        yum:\r\n          name: \"{{ web_package }}\"\r\n          state: latest\r\n        ignore_errors: yes\r\n    \r\n    - name: Install {{ db_package }} package\r\n        yum:\r\n          name: \"{{ db_package }}\"\r\n          state: latest<\/pre>\n<p>Run the playbook to ensure the first two tasks are skipped.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Check http installation status] ******************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": true, \"cmd\":\r\n[\"yum\", \"list\", \"installed\", \"http\"], \"delta\": \"0:00:00.269811\", \"end\":\r\n\"2016-05-18 07:10:18.446872\", \"failed\": true, \"rc\": 1, \"start\": \"2016-05-18\r\n07:10:18.177061\", \"stderr\": \"Error: No matching Packages to list\", \"stdout\":\r\n\"Loaded plugins: langpacks, search-disabled-repos\", \"stdout_lines\": [\"Loaded\r\nplugins: langpacks, search-disabled-repos\"], \"warnings\": [\"Consider using yum\r\nmodule rather than running yum\"]}\r\n...ignoring\r\nTASK [Install http package] ****************************************************\r\nfatal: [servera.lab.example.com]: FAILED! =&gt; {\"changed\": false, \"failed\": true,\r\n\"msg\": \"No Package matching 'http' found available, installed or updated\",\r\n\"rc\": 0, \"results\": []}\r\n...ignoring\r\n... Output omitted ...<\/pre>\n<p>Update the task that installs the <em>mariadb-server<\/em> package. Add a condition that indicates that the task should be considered as failed if the keyword Error is present in the variable command_result. The playbook should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: databases\r\n    vars:\r\n      web_package: http\r\n      db_package: mariadb-server\r\n      db_service: mariadb\r\n\r\n  tasks:\r\n    - name: Check {{ web_package }} installation status \r\n        command: yum list installed \"{{ web_package }}\" \r\n      register: command_result \r\n      ignore_errors: yes\r\n\r\n    - name: Install {{ web_package }} package\r\n        yum:\r\n          name: \"{{ web_package }}\"\r\n          state: latest\r\n        ignore_errors: yes\r\n    \r\n    - name: Install {{ db_package }} package\r\n        yum:\r\n          name: \"{{ db_package }}\"\r\n          state: latest\r\n        when: \"'Error' in command_result.stdout\"<\/pre>\n<p>Before running the playbook, run an ad hoc command to remove the <em>mariadb-server<\/em> package from the databases managed hosts.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-failures]$ ansible databases -a 'yum -y remove mariadbserver'\r\nservera.lab.example.com | SUCCESS | rc=0 &gt;&gt;\r\n... Output omitted ...\r\nRemoved:\r\nmariadb-server.x86_64 1:5.5.44-2.el7\r\n... Output omitted ...<\/pre>\n<p>Run the playbook and watch how the latest task is skipped as well.<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation dev-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Install mariadb-server package] ******************************************\r\nskipping: [servera.lab.example.com]\r\n... Output omitted ...<\/pre>\n<p>Update the task that installs the mariadb-server package by overriding what triggers the changed state. To do so, use the return code saved in the <code>command_result.rc<\/code> variable.<\/p>\n<p>Update the last task by commenting out the when condition and adding a<br \/>\n<code>changed_when<\/code> condition in order to override the changed state for the task. The condition will use the return code the registered variable contains. The playbook should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- hosts: databases\r\n    vars:\r\n      web_package: http\r\n      db_package: mariadb-server\r\n      db_service: mariadb\r\n\r\n  tasks:\r\n    - name: Check {{ web_package }} installation status \r\n        command: yum list installed \"{{ web_package }}\" \r\n      register: command_result \r\n      ignore_errors: yes\r\n\r\n    - name: Install {{ web_package }} package\r\n        yum:\r\n          name: \"{{ web_package }}\"\r\n          state: latest\r\n        ignore_errors: yes\r\n    \r\n    - name: Install {{ db_package }} package\r\n        yum:\r\n          name: \"{{ db_package }}\"\r\n          state: latest\r\n        # when: \"'Error' in command_result.stdout\"\r\n        changed_when: \"command_result.rc == 1\"\r\n<\/pre>\n<p>Execute the playbook twice; the first execution will reinstall the <em>mariadb-server<\/em> package. The following output shows the result of the second execution; as you can see, the task is marked as changed despite the fact that the <em>mariadb-server<\/em> package has already been installed:<\/p>\n<pre class=\"lang:sh decode:true \">... Output omitted ...\r\nTASK [Install mariadb-server package] ******************************************\r\nchanged: [servera.lab.example.com]\r\nPLAY RECAP *********************************************************************\r\nservera.lab.example.com : ok=4 changed=1 unreachable=0 failed=0<\/pre>\n<p>&nbsp;<\/p>\n<p>Update the playbook by nesting the first two tasks in a block cause. Remove the lines that use the <code>ignore_errors<\/code> conditional.<\/p>\n<p>Nest the task that installs the <em>mariadb-server<\/em> package in a <em>rescue<\/em> clause and remove the conditional that overrides the changed result. The task will be executed even if the previous tasks fail.<\/p>\n<p>Finally, add an always clause that will start the database server upon installation using the service module.<\/p>\n<p>Once updated, the task section should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">tasks:\r\n  - block:\r\n      - name: Check {{ web_package }} installation status\r\n          command: yum list installed \"{{ web_package }}\"\r\n        register: command_result\r\n\r\n      - name: Install {{ web_package }} package\r\n          yum:\r\n            name: \"{{ web_package }}\"\r\n            state: latest\r\n    \r\n    rescue:\r\n      - name: Install {{ db_package }} package\r\n          yum:\r\n            name: \"{{ db_package }}\"\r\n            state: latest\r\n\r\n    always:\r\n      - name: Start {{ db_service }} service\r\n          service:\r\n            name: \"{{ db_service }}\"\r\n            state: started<\/pre>\n<p>Before running the playbook, remove the mariadb-server package from the databases managed hosts.<\/p>\n<pre class=\"lang:sh decode:true\">[student@workstation dev-failures]$ ansible databases -a 'yum -y remove mariadbserver'<\/pre>\n<p>Run the playbook, and watch as despite failure for the first two tasks, Ansible installs the mariadb-server package and starts the mariadb service.<\/p>\n<pre class=\"lang:sh decode:true\">[student@workstation dev-failures]$ ansible-playbook playbook.yml\r\n... Output omitted ...\r\nTASK [Install mariadb-server package] ******************************************\r\nchanged: [servera.lab.example.com]\r\nTASK [Start mariadb service ] **************************************************\r\nchanged: [servera.lab.example.com]\r\n... Output omitted ...\r\nEvaluation<\/pre>\n<p>&nbsp;<\/p>\n<p><span style=\"color: #3366ff;\">Example 4 . Implementing Task Control<\/span><\/p>\n<p>In this lab, you will install the Apache web server and secure it using <em>mod_ssl<\/em>. You will use various Ansible conditionals to deploy the environment.<\/p>\n<p><strong>Defining tasks for the web server<\/strong><br \/>\nIn the top-level directory for this lab, create the <code>install_packages.yml<\/code> task<br \/>\nfile. Start by defining the task that uses the <em>yum<\/em> module in order to install the latest version of the httpd and mod_ssl packages. For the packages, use a loop and two variables: <em>web_package<\/em> and <em>ssl_package<\/em>. The two variables will be set by the main playbook.<\/p>\n<p>Add a when clause in order to install the packages only if:<br \/>\n1. The managed host is in the webservers group.<br \/>\n2. The amount of memory on the managed host is greater than the amount<br \/>\nthe memory variable defines. For the amount of memory the system has, the Ansible fact <code>ansible_memory_mb.real.total<\/code> can be used.<\/p>\n<p>Finally, add the task that starts the service defined by the <em>web_service<\/em> variable. When completed, the file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- name: Installs the required packages\r\n    yum:\r\n      name: \"{{ item }}\"\r\n    with_items:\r\n      - \"{{ web_package }}\"\r\n      - \"{{ ssl_package }}\"\r\n    when:\r\n      - inventory_hostname in groups[\"webservers\"]\r\n      - \"{{ ansible_memory_mb.real.total }} &gt; {{ memory }}\"\r\n\r\n- name: Starts the service\r\n    service:\r\n      name: \"{{ web_service }}\"\r\n      state: started<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Defining tasks for the web server&#8217;s configuration<\/strong><br \/>\nCreate the <code>configure_web.yml<\/code> tasks file. Start with a task that uses the <em>shell<\/em> module to determine whether or not the <em>httpd<\/em> package is installed and register the output in a variable. Update the condition to consider the task as failed based on the return code of the command (the return code is 1 when a package is not installed). The <em>failed_when<\/em> variable will be used to override how Ansible should consider the task as failed by using the return code.<\/p>\n<p>Create a block that executes only if the <em>httpd<\/em> package is installed (use the return code that has been captured in the first task). The\u00a0 block contains the tasks for configuring the files. Start the block with a task that uses the <em>get_url<\/em> module to retrieve the Apache SSL configuration file. Use the <em>https_uri<\/em> variable for the url and <code>\/etc\/httpd\/conf.d\/<\/code> for the remote path on the managed host.<\/p>\n<p>Define a task that creates <code>\/etc\/httpd\/conf.d\/ssl<\/code> remote ssl directory with a mode of <em>0755<\/em>. The directory will store the SSL certificates. Define a task that creates<code> \/var\/www\/html\/logs<\/code> remote logs directory with a mode of <em>0755<\/em>. The directory will store the SSL logs.\u00a0 Define a task that uses the stat module to ensure the<code> \/etc\/httpd\/conf.d\/ssl.conf<\/code> file exists. Capture the output in the <em>ssl_file<\/em> variable using the <em>register<\/em> statement.<\/p>\n<p>Define a task that renames the <code>\/etc\/httpd\/conf.d\/ssl.conf<\/code> file as <code>\/etc\/httpd\/conf.d\/ssl.conf.bak<\/code>, only if the file exists. Before attempting to rename the file, the task will use previous task which will evaluate the content of the <em>ssl_file<\/em> variable.<\/p>\n<p>Define another task that uses the <em>unarchive<\/em> module to retrieve the remote SSL configuration files. Use the <em>ssl_uri<\/em> variable for the source and<code> \/etc\/httpd\/<\/code><code>conf.d\/ssl\/<\/code> as the destination. Instruct the task to notify the <em>restart_services <\/em>handler when the file has been copied.<\/p>\n<p>Add the last task that creates the <code>index.html<\/code> file under <code>\/var\/www\/html\/<\/code> on the managed host.\u00a0The page should use Ansible facts and should read as follows:<\/p>\n<pre class=\"lang:sh decode:true \">severb.lab.example.com (172.25.250.11) has been customized by Ansible<\/pre>\n<p>Use the two following Ansible facts to create the page: <em>ansible_fqdn<\/em> and<br \/>\n<em>ansible_default_ipv4.address<\/em>.<\/p>\n<p>Finally, make sure the block only runs if the <em>httpd<\/em> package is installed. To do so, add a <em>when<\/em> clause that parses the return code contained in the <em>rpm_check<\/em> registered variable. When completed, the file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true \">---\r\n- shell:\r\n    rpm -q httpd\r\n  register: rpm_check\r\n  failed_when: rpm_check.rc == 1\r\n\r\n- block:\r\n    - get_url:\r\n        url: \"{{ https_uri }}\"\r\n        dest: \/etc\/httpd\/conf.d\/\r\n\r\n    - file:\r\n        path: \/etc\/httpd\/conf.d\/ssl\r\n        state: directory\r\n        mode: 0755\r\n\r\n    - file:\r\n        path: \/var\/www\/html\/logs\r\n        state: directory\r\n        mode: 0755\r\n\r\n    - stat:\r\n        path: \/etc\/httpd\/conf.d\/ssl.conf\r\n        register: ssl_file\r\n\r\n    - shell:\r\n        mv \/etc\/httpd\/conf.d\/ssl.conf \/etc\/httpd\/conf.d\/ssl.conf.bak\r\n      when: ssl_file.stat.exists\r\n\r\n    - unarchive:\r\n        src: \"{{ ssl_uri }}\"\r\n        dest: \/etc\/httpd\/conf.d\/ssl\/\r\n        copy: no\r\n      notify:\r\n        - restart_services\r\n\r\n    - copy:\r\n        content: \"{{ ansible_fqdn }} ({{ ansible_default_ipv4.address }}) has been customized by Ansible\\n\"\r\n        dest: \/var\/www\/html\/index.html\r\n   when:\r\n     rpm_check.rc == 0<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Defining tasks for the firewall<\/strong><br \/>\nCreate the <code>configure_firewall.yml<\/code> task file. Define the task that uses the <em>yum<\/em> module to install the package that the <em>fw_package<\/em> variable defines (latest version of the <em>firewall<\/em> service &#8211; the variable will be set in the main playbook). Tag the task with the production tag.<\/p>\n<p>Add the task that starts the firewall service using the <em>fw_service<\/em> variable and tag it as production.<\/p>\n<p>Write the task that uses the <em>firewalld<\/em> module to add the <em>http<\/em> and <em>https<\/em> service rules to the firewall. The rules should be applied immediately as well as persistently. Use a loop for the two rules. Tag the task as <em>production<\/em>.<br \/>\nWhen completed, the file should read as follows:<\/p>\n<pre class=\"lang:sh decode:true\">---\r\n- yum:\r\n    name: \"{{ fw_package }}\"\r\n    state: latest\r\n  tags: production\r\n\r\n- service:\r\n    name: \"{{ fw_service }}\"\r\n    state: started\r\n  tags: production\r\n\r\n- firewalld:\r\n    service: \"{{ item }}\"\r\n    immediate: true\r\n    permanent: true\r\n    state: enabled\r\n  with_items:\r\n    - http\r\n    - https\r\n  tags: production<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Defining the main playbook<\/strong><br \/>\nCreate the main <code>playbook.yml<\/code> and start by targeting the hosts in the <em>webservers<\/em> host group. Define a block that imports the following three task files: <code>install_packages.yml<\/code>, <code>configure_web.yml<\/code>, and <code>configure_firewall.yml<\/code>, using the <em>include<\/em> statement.<\/p>\n<p>For the first include, use<code> install_packages.yml<\/code> as the name of the file to import. Define the four variables required by the file:\u00a0 <em>memory<\/em> with a value of <em>256,\u00a0 web_package<\/em> with a value of <em>httpd<\/em>, <em>ssl_package<\/em> with a value of <em>mod_ssl<\/em>, <em>web_service<\/em> with a value of <em>httpd.<\/em><\/p>\n<p>For the task that imports the <code>configure_web.yml<\/code> file, define the following variables: <em>https_uri<\/em> with a value of <code>http:\/\/materials.example.com\/task_control\/https.conf<\/code>, and <em>ssl_uri<\/em> with a value of <code>http:\/\/materials.example.com\/task_control\/ssl.tar.gz<\/code>.<\/p>\n<p>For the task that imports the <code>configure_firewall.yml<\/code> playbook, add a condition to only import the tasks tagged with the <em>production<\/em> tag. Define the <em>fw_package<\/em> and <em>fw_service<\/em> variables with a value of <em>firewalld<\/em>.<\/p>\n<p>Create the <em>rescue<\/em> clause for the block that installs the latest version of the <em>httpd<\/em> package and notifies the <em>restart_services<\/em> handler upon the package installation. Add a debug statement that reads: <em>Failed to import and run all the tasks; installing the web server manually<\/em><\/p>\n<p>Add an <em>always<\/em> statement that uses the <em>shell<\/em> module to query the status of the <em>httpd<\/em> service using <em>systemctl<\/em>.<\/p>\n<p>Define the <em>restart_services<\/em> handler that uses a loop to restart both the<br \/>\n<em>firewalld<\/em> and <em>httpd<\/em> services.<\/p>\n<p>When completed, the playbook should read as follows:<\/p>\n<pre class=\"lang:sh decode:true \">---\r\n- hosts: webservers\r\n    tasks:\r\n      - block:\r\n          - include: install_packages.yml\r\n            vars:\r\n              memory: 256\r\n              web_package: httpd\r\n              ssl_package: mod_ssl\r\n              web_service: httpd\r\n          - include: configure_web.yml\r\n            vars:\r\n              https_uri: http:\/\/materials.example.com\/task_control\/https.conf\r\n              ssl_uri: http:\/\/materials.example.com\/task_control\/ssl.tar.gz\r\n          - include: configure_firewall.yml\r\n            vars:\r\n              fw_package: firewalld\r\n              fw_service: firewalld\r\n            tags: production\r\n\r\n        rescue:\r\n          - yum:\r\n              name: httpd\r\n              state: latest\r\n            notify:\r\n              - restart_services\r\n          - debug:\r\n              msg: \"Failed to import and run all the tasks; installing the web server manually\"\r\n\r\n        always:\r\n          - shell:\r\n              cmd: \"systemctl status httpd\"\r\n\r\n    handlers:\r\n      - name: restart_services\r\n          service:\r\n            name: \"{{ item }}\"\r\n            state: restarted\r\n         with_items:\r\n           - httpd\r\n           - firewalld<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Executing the playbook.yml playbook<\/strong><br \/>\nRun the <code>playbook.yml<\/code> playbook to set up the environment. The playbook should:<br \/>\n\u2022 Import and run the tasks that install the web server packages only if there is enough memory on the managed host.<br \/>\n\u2022 Import and run the tasks that configure SSL for the web server.<br \/>\n\u2022 Import and run the tasks that create the firewall rule for the web server to be<br \/>\nreachable.<\/p>\n<pre class=\"lang:sh decode:true\">[student@workstation lab-task-control]$ ansible-playbook playbook.yml\r\nPLAY ***************************************************************************\r\nTASK [setup] *******************************************************************\r\nok: [serverb.lab.example.com]\r\n...\r\nRUNNING HANDLER [restart_services] *********************************************\r\nchanged: [serverb.lab.example.com] =&gt; (item=httpd)\r\nchanged: [serverb.lab.example.com] =&gt; (item=firewalld)\r\nPLAY RECAP *********************************************************************\r\nserverb.lab.example.com : ok=19 changed=13 unreachable=0 failed=0<\/pre>\n<p>Ensure the web server has been correctly configured by querying the home page of the web server (https:\/\/serverb.example.com) using <em>curl<\/em> with the <em>-k<\/em> option to allow insecure connections (bypass any SSL strict checking):<\/p>\n<pre class=\"lang:sh decode:true \">[student@workstation lab-task-control]$ curl -k https:\/\/serverb.lab.example.com\r\nserverb.lab.example.com (172.25.250.11) has been customized by Ansible\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><!--EndFragment--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ansible evaluates the return codes of modules and commands to determine whether a task succeeded or failed. Normally, when a task fails Ansible immediately aborts the plays, skipping all subsequent tasks. However, sometimes an administrator may want to have playbook execution continue even if a task fails. For example, it may be expected that a &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/miro.borodziuk.eu\/index.php\/2020\/02\/06\/ansible-handling-errors-2\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Ansible Handling Errors&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3499,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86],"tags":[],"_links":{"self":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3497"}],"collection":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/comments?post=3497"}],"version-history":[{"count":37,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3497\/revisions"}],"predecessor-version":[{"id":3537,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/posts\/3497\/revisions\/3537"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media\/3499"}],"wp:attachment":[{"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/media?parent=3497"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/categories?post=3497"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/miro.borodziuk.eu\/index.php\/wp-json\/wp\/v2\/tags?post=3497"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}