07Ansible condition judgment, loop, handlers, task failure, file management

1. Conditional task syntax

In the example, ansible_ The distribution variable is a fact determined during the Gathering Facts task and is used to identify the operating system branch of the managed host. The variable platform is created by the playbook and contains the operating system distribution list supported by the playbook. If ansible_ If the value of distribution is in the platform list, the condition passes and the task runs.

---
- hosts: 192.168.8.132
  vars:
    platform:
      - redhat
      - CentOS
  tasks:
    - name: test
      dnf:
        name: vsftpd
      when: ansible_facts['distribution'] in platform

Note the indentation of the when statement. Since the when statement is not a module variable, it must be indented to the highest level of the task and placed outside the module.

The task is a YAML hash / dictionary, and the when statement is just another key in the task, just like the name of the task and the module it uses. The usual practice is to put any possible when keyword after the task name and module (and module parameters).

2. Implement handling procedures

2.1ansible handler

Ansible modules are designed to be idempotent. This means that in a properly written playbook, the playbook and its tasks can run multiple times without changing the managed host unless changes are needed to bring the managed host into the desired state.

However, when the task does change the system, it may be necessary to run further tasks. For example, changing a service profile may require that the service be reloaded for its changed configuration to take effect.

A handler is a task that responds to notifications triggered by other tasks. The task notifies its handler only when it changes something on the managed host. Each handler has a globally unique name and is triggered at the end of the task block in the playbook. If no task notifies the handler by name, the handler will not run. If one or more tasks notify the handler, the handler runs once after all other tasks in play are completed. Because handlers are tasks, you can use the modules they will use for any other task in the handler. Typically, the handler is used to reboot the host and restart the service.

A handler can be considered an inactive task and is triggered only when explicitly invoked with a notify statement. In the following code snippet, the restart apache handler will restart the Apache server only if the configuration file is updated and the task is notified:

---
- hosts: 192.168.8.132
  vars:
  tasks:
    - name: install httpd
      dnf:
        name: httpd
        state: present

    - name: config httpd
      copy:
        src: /opt/project/playbook/httpd/files/httpd-vhosts.conf
        dest: /etc/httpd/conf.d/vhost.conf

    - name: service for httpd
      service:
        name: httpd
        state: started

[root@master playbook]# mkdir httpd
[root@master playbook]# cd httpd/
[root@master httpd]# ls
[root@master httpd]# ls
[root@master httpd]# mkdir files
[root@master httpd]# cd files/
[root@master files]# scp 192.168.8.132:/usr/share/doc/httpd/httpd-vhosts.conf .
root@192.168.8.132's password: 
httpd-vhosts.conf                                             100% 1477   643.1KB/s   00:00  

//Modify profile
<VirtualHost *:80>
    DocumentRoot "/var/www/html/abc"
    ServerName web.example.com
</VirtualHost>

//Create access directory on controlled machine
[root@localhost html]# mkdir abc
[root@localhost html]# echo 'test page1' > abc/index.html
[root@localhost html]# mkdir pyd
[root@localhost html]# echo 'test page2' > pyd/index.html
[root@master project]# ansible-playbook playbook/test.yml 


Using the notify statement

---
- hosts: 192.168.8.132
  vars:
  tasks:
    - name: install httpd
      dnf:
        name: httpd
        state: present

    - name: config httpd
      copy:
        src: /opt/project/playbook/httpd/files/httpd-vhosts.conf
        dest: /etc/httpd/conf.d/vhost.conf
      notify:
        - restart httpd

    - name: service for httpd
      service:
        name: httpd
        state: started
        enabled: yes
  handlers:
    - name: restart httpd
      service:
        name: httpd
        state: restarted

Now the task configuration file has not been modified. You can see that it does not trigger the restart of the service

[root@master project]# ansible-playbook playbook/test.yml 

PLAY [192.168.8.132] ***************************************************************************

TASK [Gathering Facts] *************************************************************************
ok: [192.168.8.132]

TASK [install httpd] ***************************************************************************
ok: [192.168.8.132]

TASK [config httpd] ****************************************************************************
ok: [192.168.8.132]

TASK [service for httpd] ***********************************************************************
ok: [192.168.8.132]

PLAY RECAP *************************************************************************************
192.168.8.132              : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Modify profile

[root@master project]# vim playbook/httpd/files/httpd-vhosts.conf 
#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost container.
# The first VirtualHost section is used for all requests that do not
# match a ServerName or ServerAlias in any <VirtualHost> block.
#
<VirtualHost *:80>
    DocumentRoot "/var/www/html/abc"
    ServerName web.example.com
</VirtualHost>

Listen 8080
<VirtualHost *:8080>
    DocumentRoot "/var/www/html/pyd"
    ServerName www.example.com
</VirtualHost>

The trigger detects the modification of the configuration file and restarts the service

[root@master project]# ansible-playbook playbook/test.yml 

PLAY [192.168.8.132] ***************************************************************************

TASK [Gathering Facts] *************************************************************************
ok: [192.168.8.132]

TASK [install httpd] ***************************************************************************
ok: [192.168.8.132]

TASK [config httpd] ****************************************************************************
changed: [192.168.8.132]

TASK [service for httpd] ***********************************************************************
ok: [192.168.8.132]

RUNNING HANDLER [restart httpd] ****************************************************************
changed: [192.168.8.132]

PLAY RECAP *************************************************************************************
192.168.8.132              : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@master project]# 

2.2 benefits of using handlers

There are several important things to keep in mind when using handlers:

  • Handlers always run in the order specified in the handlers section of play. They do not run in the order listed by the notify statement * in the task, or in the order notified to them by the task.
  • The handler typically runs after all other tasks in the associated play have been completed. The handler called by a task in the tasks section of the playbook will not run until all tasks under the tasks have been processed.
  • The handler name exists in each play namespace. If two handlers are mistakenly given the same name, only one will run.
  • Even if there are multiple task notification handlers, the handler runs only once. If there is no task notification handler, it will not run.
  • If the task that contains the notify statement does not report the changed result (for example, the package is installed and the task reports ok), the handler is not notified. The handler will be skipped until it is notified by another task. Ansible notifies the handler only if the related task reports the changed status.

The handler is used to perform additional actions when the task makes changes to the managed host. They should not be used as a substitute for normal tasks.

3. Failed to process task

3.1 task errors in management play

Ansible evaluates the return code of the task to determine whether the task succeeded or failed. Generally speaking, when a task fails, ansible will immediately abort the rest of the play on the host and skip all subsequent tasks.

But sometimes, you may want to continue play ing even if the task fails. For example, you might expect a pending task to fail and want to fix it by conditionally running some other task.

Ansible has several functions to manage task errors.

3.2 ignore task failure

---
- hosts: 192.168.8.132
  vars:
  tasks:
    - name: install httpd
      dnf:
        name: httpd2   
        state: present
      ignore_errors: yes

3.3 enforce handler after task failure

In general, if a task fails and play is aborted on that host, the handler that receives notification of an earlier task in play will not run. If force is set in play_ Handlers: Yes keyword, the notified handler will be called even if play is aborted due to subsequent task failure.

The following code snippet demonstrates how to use force in play_ Handlers keyword to force the corresponding handler when the task fails:

---
- hosts: 172.16.103.129
  force_handlers: yes
  tasks:
    - name: a task which always notifies its handler
      command: /bin/true
      notify: restart the database
      
    - name: a task which fails because the package doesn't exist
      yum:
        name: notapkg
        state: latest
        
  handlers:
    - name: restart the database
      service:
        name: mariadb
        state: restarted

Remember that the handler is notified when the task reports the changed result, but not when the task reports the ok or failed result.

3.4 specified task failure conditions

Failed can be used in tasks_ When keyword to specify a condition indicating that the task has failed. This is usually used in conjunction with command modules, which may successfully execute a command, but the output of the command may indicate failure.

For example, you can run a script that outputs an error message and use that message to define the failure status of a task. The following code snippet demonstrates how to use failed in a task_ When keyword:

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      command: /root/test.sh
      register: result      //Printout results

    - debug:
        var: result

[root@master project]# ansible-playbook playbook/tom.yml 

PLAY [192.168.8.132] ***************************************************************************

TASK [test] ************************************************************************************
changed: [192.168.8.132]

TASK [debug] ***********************************************************************************
ok: [192.168.8.132] => {
    "result": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/libexec/platform-python"
        },
        "changed": true,
        "cmd": [
            "/root/test.sh"
        ],
        "delta": "0:00:00.026520",
        "end": "2021-07-27 16:03:24.502750",
        "failed": false,
        "rc": 0,
        "start": "2021-07-27 16:03:24.476230",
        "stderr": "useradd: User“ tom"Already exists",
        "stderr_lines": [
            "useradd: User“ tom"Already exists"
        ],
        "stdout": "Change user tom Your password.\npasswd: All authentication tokens have been successfully updated.",
        "stdout_lines": [
            "Change user tom Your password.",
            "passwd: All authentication tokens have been successfully updated."
        ]
    }
}

PLAY RECAP *************************************************************************************
192.168.8.132              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      command: /root/test.sh
      register: result
      failed_when: "'Already exists' in result.stderr"

[root@master project]# ansible-playbook playbook/tom.yml 

PLAY [192.168.8.132] ***************************************************************************

TASK [test] ************************************************************************************
fatal: [192.168.8.132]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": true, "cmd": ["/root/test.sh"],
 "delta": "0:00:00.024721", "end": "2021-07-27 16:06:07.102527", "failed_when_result": true, 
 "rc": 0, "start": "2021-07-27 16:06:07.077806", "stderr": "useradd: User“ tom"Already exists", "stderr_lines":
  ["useradd: User“ tom"Already exists"], "stdout": "Change user tom Your password.\npasswd: All authentication tokens have been successfully updated.",
   "stdout_lines": ["Change user tom Your password.
   ", "passwd: All authentication tokens have been successfully updated."]}

PLAY RECAP *************************************************************************************
192.168.8.132              : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

The fail module can also be used to force a task to fail. The above scenario can also be written as two tasks:
We can use the fail module to provide a clear failure message for the task. This method also supports deferred failures, allowing intermediate tasks to be run to complete or roll back other changes.

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      command: /root/test.sh
      register: result
      ignore_errors: yes //Ignore errors

    - name: test
      fail:
        msg: 'I have already created this user'  //If it already exists, I will output it. I have created this user
      when: "'Already exists' in result.stderr" //If the judgment condition is already in, it is in result Stderr outputs the contents of the previous command

[root@master project]# ansible-playbook playbook/tom.yml 

PLAY [192.168.8.132] ***************************************************************************

TASK [test] ************************************************************************************
changed: [192.168.8.132]

TASK [test] ************************************************************************************
fatal: [192.168.8.132]: FAILED! 
=> {"changed": false, "msg": "I have already created this user"}

PLAY RECAP *************************************************************************************
192.168.8.132              : ok=1    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

[root@master project]# 

3.5 specify when the task reports "Changed" results

When the task makes changes to the managed host, it reports the changed status and notifies the handler. If the task does not require changes, it reports ok and does not notify the handler.

changed_ The when keyword can be used to control when a task reports that it has changed. For example, the shell module in the next example will be used to obtain Kerberos credentials for subsequent tasks. It usually always reports changed at run time. To resist this change, set changed_when: false so that it only reports ok or failed.

The following example uses a shell module to report changed based on the output of the module collected through registered variables:

[root@master project]# ansible httpd -m shell -a 'echo "redhat"|passwd --stdin tom'
192.168.8.132 | CHANGED | rc=0 >>
Change user tom Your password.
passwd: All authentication tokens have been successfully updated.

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      shell: echo "redhat"|passwd --stdin tom
      register: result
      changed_when: "'All authentication tokens have been successfully updated' not in result.stdout" //If all authentication tokens have been successfully updated, output ok
      notify:
        - print info

  handlers:
     - name: print info
       debug:
         msg: "Password setting succeeded"

3.6Ansible blocks and error handling

In playbook, blocks are clauses that logically group tasks, and can be used to control how tasks are executed. For example, a task block can contain the when keyword to apply a condition to multiple tasks:

- name: block example
  hosts: 172.16.103.129
  tasks:
    - name: installing and configuring Yum versionlock plugin
      block: //When you use block, the following two tasks are a whole
      - name: package needed by yum
        yum:
          name: yum-plugin-versionlock
          state: present
      - name: lock version of tadata
        lineinfile:
          dest: /etc/yum/pluginconf.d/versionlock.list
          line: tzdata-2020j-1
          state: present
      when: ansible_distribution == "Redhat" //I judge once for every task I perform

Through the block, you can also combine the rescue and always statements to handle errors. If any task in the block fails, perform the task in its rescue block to recover. After the task in the block clause and the task in the rescue clause (if there is a failure) run, the task in the always clause runs. Summary:

  • block: defines the main task to run
  • rescue: defines the task to run when the task defined in the block clause fails
  • Always: defines tasks that always run independently, regardless of whether the tasks defined in the block and rescue clauses succeed or fail

The following example demonstrates how to implement blocks in playbook. Even if the task defined in the block clause fails, the task defined in the rescue and always clauses will execute.

tasks:
  - name: Upgrade DB
    block:
      - name: upgrade the database
        shell:
          cmd: /usr/local/lib/upgrade-database
    rescue:
      - name: revert the database upgrade
        shell:
          cmd: /usr/local/lib/revert-database
    always:
      - name: always restart the database
        service:
          name: mariadb
          state: restarted

when condition in block is also applied to its rescue and always clauses, if any.

4. Create a file or directory on the managed node

4.1 description document module

The Files module library contains modules that allow users to complete most tasks related to Linux file management, such as creating, copying, editing, and modifying file permissions and other properties. The following table provides a list of common file management modules:

Common file module

Module nameModule description
blockinfileInserts, updates, or deletes a multiline text block surrounded by customizable marker lines
copyCopy files from a local or remote computer to a location on the managed host. Similar to the file module, the copy module can also set file attributes, including SELinux upper and lower files.
fetchThis module functions similar to the copy module, but works in the opposite way. This module is used to obtain files from a remote computer to the control node and store them in a file tree organized by host name.
fileSet attributes such as permissions, ownership, SELinux context, and timestamp of general files, symbolic links, hard links, and directories. This module can also create or delete general files, symbolic links, hard links, and directories. Several other file related modules support the same property setting options as the file module, including the copy module.
lineinfileMake sure that a specific line is in a file, or replace an existing line with a back reference regular expression. This module is mainly used when the user wants to change a line of the file.
statRetrieve the status information of the file, similar to the stat command in Linux.
synchronizeA wrapper around the rsync command to speed up and simplify common tasks. The synchronize module does not provide access to the full functionality of the rsync command, but it is true that the most common calls are easier to implement. The user may still need to call the rsync command directly through the run command module.

4.2 automation example of files module

Creating, copying, editing, and deleting Files on the managed host is a common task that users can implement using the modules in the Files module library.
The following example shows how these modules can be used to automate common file management tasks.

4.3 ensure that files exist on the managed host

Use the file module to process files on the managed host. Its working mode is similar to the touch command. If it does not exist, an empty file is created. If it does exist, its modification time is updated. In this example, in addition to working with files, Ansible ensures that the owner, group, and permissions of the file are set to specific values.

- name: Touch a file and set permissions
  file:
    path: /path/to/file
    owner: user1
    group: group1
    mode: 0640
    state: touch

4.3 copying and editing files on managed hosts

Copy files from managed host to host

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      fetch:
        src: /root/test.sh
        dest:/root/

You can see the folder named after the managed host IP

[root@master project]# ls /root/
1              Template document desktop              apr-util-1.6.1.tar.gz  initial-setup-ks.cfg
192.168.8.132  Video download  anaconda-ks.cfg   httpd-2.4.48.tar.gz    playbook
 public           Picture music  apr-1.7.0.tar.gz  httpd.conf

To add a text block to an existing file, use the blockinfile module:
Note: when using the block infile module, comment block tags are inserted at the beginning and end of the block to ensure idempotency

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      blockinfile:
        path: /tmp/abc
        block: |
          hello world
          hello pyd
        state: present
        create: yes

[root@localhost ~]# cat /tmp/abc 
# BEGIN ANSIBLE MANAGED BLOCK
hello world
hello pyd
# END ANSIBLE MANAGED BLOCK
[root@localhost ~]# 

4.4 deleting files from managed hosts

A basic example of deleting a file from a managed host is to use the file module and the state: absent parameter. The state parameter is optional for many modules. Some modules also support other options.

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      file:
        path: /tmp/abc
        state: absent

4.5 retrieving the file status on the managed host

The fact that the stat module retrieves files is similar to the stat command in Linux. Parameters provide functions such as retrieving file attributes, determining file attributes, and checking.

The stat module returns a hash dictionary containing the values of the file status data, allowing users to reference each piece of information with separate variables.

The following example registers the results of the stat module and then displays the MD5 validation and of the files it checks.

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
    - name: test
      stat:
        path: /root/test.sh
      register: result
    - debug:
        var: result

[root@master project]# ansible-playbook playbook/test.yml 

PLAY [192.168.8.132] ***************************************************************************

TASK [test] ************************************************************************************
ok: [192.168.8.132]

TASK [debug] ***********************************************************************************
ok: [192.168.8.132] => {
    "result": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/libexec/platform-python"
        },
        "changed": false,
        "failed": false,
        "stat": {
            "atime": 1627372025.17479,
            "attr_flags": "",
            "attributes": [],
            "block_size": 4096,
            "blocks": 8,
            "charset": "us-ascii",
            "checksum": "42579296c474f32c63a3d795e48236a8f5ecb4a3",
            "ctime": 1627372019.9387903,
            "dev": 64768,
            "device_type": 0,
            "executable": true,
            "exists": true,
            "gid": 0,
            "gr_name": "root",
            "inode": 101432870,
            "isblk": false,
            "ischr": false,
            "isdir": false,
            "isfifo": false,
            "isgid": false,
            "islnk": false,
            "isreg": true,
            "issock": false,
            "isuid": false,
            "mimetype": "text/x-shellscript",
            "mode": "0755",
            "mtime": 1627372011.0287907,
            "nlink": 1,
            "path": "/root/test.sh",
            "pw_name": "root",
            "readable": true,
            "rgrp": true,
            "roth": true,
            "rusr": true,
            "size": 59,
            "uid": 0,
            "version": "1246841604",
            "wgrp": false,
            "woth": false,
            "writeable": true,
            "wusr": true,
            "xgrp": true,
            "xoth": true,
            "xusr": true
        }
    }
}

PLAY RECAP *************************************************************************************
192.168.8.132              : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@master project]# 

4.6 synchronize files between the control node and the managed host

The synchronize module is a wrapper around rsync tools that simplifies common file management tasks in playbook. The rsync tool must be installed on both the local and remote hosts. By default, when using the synchronize module, the "local host" is the host from which the synchronization task originates (usually the control node), and the "target host" is the host to which synchronize is connected.

The following example synchronizes a file located in the Ansible working directory to the managed host:

---
- hosts: 192.168.8.132
  gather_facts: no
  tasks:
  tasks:
    - name: test
      yum:
        name: rsync
        state: present

    - name: test
      synchronize:
        src: httpd
        dest: /opt

[root@localhost ~]# ls /opt/
httpd

There are many ways to use the synchronize module and its many parameters, including synchronizing directories. Run the ansible doc synchronize command to see additional parameters and playbook examples

Keywords: Linux

Added by ricmch on Wed, 12 Jan 2022 11:31:03 +0200