linux一切皆文件,通过文件模块可以高效部署linux配置,服务等

1、 文件模块

ansible可以通过文件模块管理主机,通过command命令只能一次性改一台主机,但是使用文件模块可以批量修改大量主机

1.1 常用文件模块

模块名称 模块介绍
blockinfile 在文件上下添加注释
copy 将本地文件复制到远程主机
fetch 将受管节点数据拉取到本地
file 创建、删除文件,设置文件权限、SELinux等信息
lineinfile 编辑已存在的文件,可以通过正则匹配
stat 查看文件信息
synchronize 做文件同步相关操作

1.2 使用file模块

state 说明
state: touch 指定类型为普通文件
state: directory 指定类型为目录文件
state: link 指定类型为连接文件
state:absent 删除文件

1.2.1 创建一个文件

- name: 创建一个文件
  file:
    path: /etc/foo.conf
    state: touch

1.2.2 创建一个目录文件

- name: 创建一个目录文件
  file:
    path: /etc/foo
    state: directory

1.2.3 创建一个软链接

- name: 创建一个软链接
  file:
    src: /file/to/link/to
    dest: /path/to/symlink
    owner: foo
    group: foo
    state: link

1.2.4 创建一个硬链接

- name: 创建一个硬链接
  file:
    src: '/tmp/{{ item.src }}'
    dest: '{{ item.dest }}'
    state: link
  with_items:
    - { src: x, dest: y }
    - { src: z, dest: k }

1.2.5 指定文件权限

- name: 指定文件权限
  file:
    path: /etc/some_directory
    state: directory
    mode: '0755'

1.2.6 设置文件SELinux

- name: 创建一个文件
  file:
    path: /etc/foo.conf
    state: touch
    setype: samba_share_t

1.2.7 删除一个文件

- name: 删除一个文件
  file:
    path: /etc/foo.conf
    state: absent

1.2.7 file模块小练习

  1. 创建一个文件,权限0644
  2. 使用stat模块查看
[student@workstation control-review]$ cat mmx.yml
---
- name: 文件模块
  hosts: all
  tasks:
          - name: 创建一个文件
            file:
                    path: /root/mmx
                    state: touch
                    mode: 0644
[student@workstation control-review]$ ansible all -a 'ls  -al /root/mmx'
serverb.lab.example.com | CHANGED | rc=0 >>
-rw-r--r--. 1 root root 0 Aug 14 11:20 /root/mmx

1.3 修改文件属性

给文件增加安全上下文

- name: Allow apache to modify files in /srv/git_repos
  sefcontext:
    target: '/srv/git_repos(/.*)?'
    setype: httpd_git_rw_content_t
    state: present

可以使用force:yes,开启SELinux

1.4 从远端拉取文件

[student@workstation control-review]$ cat mmx01.yml
---
- name: 文件模块
  hosts: all
  tasks:
          - name: 拉取文件
            fetch:
                    src: /etc/fstab
                    dest: /home/student/control-review/
[student@workstation control-review]$ tree serverb.lab.example.com/
serverb.lab.example.com/
└── etc
    └── fstab

1.5 修改文件内容

lineinfile是一个功能很强大的模块

1.5.1 linefile常用参数

参数 说明
path 文件路径
regexp 通过正则匹配
line 修改的行
owner 所有者
group 所属组
mode 权限
create 是否创建
state 新增或删除

1.5.2 lineinfile添加一行内容

# 加入一行
[student@workstation control-review]$ cat mmx02.yml
---
- name: 文件模块
  hosts: all
  become: no
  tasks:
          - name: 修改文件内容
            lineinfile:
                    path: ~/xiaoming.txt
                    line: 'hello mmx!'
                    state: present
[student@workstation control-review]$ ansible all -a 'cat xiaoming.txt'
serverb.lab.example.com | CHANGED | rc=0 >>
hello xiaoming
hello mmx!

1.6 添加注释

利用blockinfile加入注释,可以通过marker在文字上下加入必要说明

[student@workstation control-review]$ cat mmx03.yml
---
- name: 文件模块
  hosts: all
  become: no
  tasks:
          - name: 创建一个文件
            file:
                    path: xiaobai.txt
                    state: touch
          - name: 加入注释
            blockinfile:
                    path: xiaobai.txt
                    block: |
                            Match User ansible-agent
                            PasswordAuthentication no
                    marker: "### hello world ###"
[student@workstation control-review]$ ansible all -a 'cat /home/devops/xiaobai.txt'
serverb.lab.example.com | CHANGED | rc=0 >>
### hello world ###
Match User ansible-agent
PasswordAuthentication no
### hello world ###

1.7 检查文件状态

[student@workstation control-review]$ cat mmx04.yml
---
- name: 检查文件状态
  hosts: all
  vars_files:
          - vars/mmx.yml
  tasks:
          - name: use stat module
            stat:
                    path: /etc/fstab
                    checksum_algorithm: "{{ stat }}"
            register: result
          - debug: msg='The checksum of the file is "{{ result.stat.checksum }}"'
[student@workstation control-review]$ ansible-playbook mmx04.yml
TASK [debug] ***********************************************************************************************************************************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com] => {
    "msg": "The checksum of the file is \"3b1d0d2a7e340e67cf2fe0fed228b5a0\""
}

1.8 同步文件

synchronize模块可以很好的同步文件,如果不满足需求的时候,可以使用命令rsync

- name: 同步文件
  synchronize:
    src: some/relative/path
    dest: /some/absolute/path

1.9 文件模块练习

1.9.1 第一题

  1. 开启实验环境,进入目录,创建一个名为secure_log_backups.yml的play
    1. secure_log_backups.yml作用于所有主机组,提权到用户root
    2. 使用fetch模块拉取/var/log/secure目录文件到本地目录secure-backups
[student@workstation file-manage]$ cat secure_log_backups.yml
---
- name: 使用fetch模块
  hosts: all
  remote_user: root
  tasks:
          - name: 拉取/var/log/secure信息
            fetch:
                    src: /var/log/secure
                    dest: secure-backups
                    falt: no
# 检查语法
[student@workstation file-manage]$ ansible-playbook --syntax-check secure_log_backups.yml

playbook: secure_log_backups.yml
# 执行playbook
[student@workstation file-manage]$ ansible-playbook secure_log_backups.yml

PLAY [使用fetch模块] ****************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com]
ok: [servera.lab.example.com]

TASK [拉取/var/log/secure信息] ******************************************************************************************************************************************************************************************************************************
changed: [serverb.lab.example.com]
changed: [servera.lab.example.com]

PLAY RECAP **********************************************************************************************************************************************************************************************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverb.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

# 查看拉取结果
[student@workstation file-manage]$ ls
ansible.cfg  files  inventory  secure-backups  secure_log_backups.yml
[student@workstation file-manage]$ tree secure-backups/
secure-backups/
├── servera.lab.example.com
│   └── var
│       └── log
│           └── secure
└── serverb.lab.example.com
    └── var
        └── log
            └── secure

6 directories, 2 files

1.9.2 第二题

  1. 创建copy_file.yml的playbook文件,将本地文件复制到目标主机

  2. copy模块参数如下

    参数
    src files/users.txt
    dest /home/devops/users.txt
    owner devops
    group devops
    mode u+rw,g-wx,o-rwx
    setype samba_share_t
[student@workstation file-manage]$ cat copy_file.yml
---
- name: use copy module
  hosts: all
  remote_user: root
  tasks:
          - name: copy filea and set attributes
            copy:
                    src: files/users.txt
                    dest: /home/devops/users.txt
                    owner: devops
                    group: devops
                    mode: u+rw,g-wx,o-rwx
                    setype: samba_share_t
# 使用-Z参数可以查看安全上下文
[student@workstation file-manage]$ ansible all -a 'ls -Z users.txt' -u devops
serverb.lab.example.com | CHANGED | rc=0 >>
unconfined_u:object_r:samba_share_t:s0 users.txt

servera.lab.example.com | CHANGED | rc=0 >>
unconfined_u:object_r:samba_share_t:s0 users.txt

1.9.3 第三题

  1. 创建selinux_defaults.yml的playbook
  2. 创建文件users.txt,设置user、role、tpye、level均为_default
[student@workstation file-manage]$ cat selinux_defaults.yml
---
- name: set file selinux
  hosts: all
  remote_user: root
  tasks:
          - name: set selinux
            file:
                    path: /home/devops/users.txt
                    seuser: _default
                    serole: _default
                    setype: _default
                    selevel: _default
# 使用ad hoc查看selinux
[student@workstation file-manage]$ ansible all -a 'ls -ldZ users.txt' -u devops
servera.lab.example.com | CHANGED | rc=0 >>
-rw-r-----. 1 devops devops unconfined_u:object_r:user_home_t:s0 0 Aug 14 16:23 users.txt

serverb.lab.example.com | CHANGED | rc=0 >>
-rw-r-----. 1 devops devops unconfined_u:object_r:user_home_t:s0 0 Aug 14 16:23 users.txt

1.9.4 第四题

  1. 创建add_line.yml的playbook
  2. 将"This line was added by the lineinfile module."添加到/home/devops/users.txt中
[student@workstation file-manage]$ cat add_line.yml
---
- name: 添加一段话
  hosts: all
  remote_user: root
  tasks:
          - name: 使用lineinfile添加一行
            lineinfile:
                    path: /home/devops/users.txt
                    line: "This line was added by the lineinfile module."
[student@workstation file-manage]$ ansible all -a 'cat ~/users.txt' -u devops
servera.lab.example.com | CHANGED | rc=0 >>
This line was added by the lineinfile module.

serverb.lab.example.com | CHANGED | rc=0 >>
This line was added by the lineinfile module.

1.9.5 第五题

  1. 创建add_block.yml添加一个说明

  2. 在users.txt下添加:

    1. This block of the consists of two lines.
    2. They have been added by the blockinfile module
[student@workstation file-manage]$ cat add_block.yml
---
- name: 添加一个说明
  hosts: all
  remote_user: root
  tasks:
          - name: 使用blockinfile模块
            blockinfile:
                    path: /home/devops/users.txt
                    block: |
                            This block of the consists of two lines
                            They have been added by the blockinfile module
[student@workstation file-manage]$ ansible all -a 'cat users.txt' -u devops
serverb.lab.example.com | CHANGED | rc=0 >>
This line was added by the lineinfile module.
# BEGIN ANSIBLE MANAGED BLOCK
This block of the consists of two lines
They have been added by the blockinfile module
# END ANSIBLE MANAGED BLOCK

servera.lab.example.com | CHANGED | rc=0 >>
This line was added by the lineinfile module.
# BEGIN ANSIBLE MANAGED BLOCK
This block of the consists of two lines
They have been added by the blockinfile module
# END ANSIBLE MANAGED BLOCK

1.9.6 第六题

  1. 创建一个remove_file.yml的文件
  2. 删除文件users.txt
[student@workstation file-manage]$ cat remove_file.yml
---
- name: 删除文件
  hosts: all
  remote_user: root
  tasks:
          - name: 删除users.txt的文件
            file:
                    path: /home/devops/users.txt
                    state: absent
[student@workstation file-manage]$ ansible all -a 'ls -l' -u devops
serverb.lab.example.com | CHANGED | rc=0 >>
total 0

servera.lab.example.com | CHANGED | rc=0 >>
total 0

2、 JINJA2 模板

通过jinja2模板可以编写一个模板,当通过template模块调用此模板,可以灵活针对不同被管理主机,一起的配置文件都可以写成j2模板形式

2.1 使用模板文件

# 如果是循环or条件判断,开头{% EXPR %},结尾{{ EXPR }}
# 包裹变量{{ 变量 }}
# 注释{# XXX #}

{# /etc/hosts line #}
{{ ansible_facts['defaults_ipv4']['address'] }}
{{ ansible_facts['hostname'] }}

2.2 创建JINJA2模板

JINJA2模板文件的后缀.j2

  1. 可以使用ansible hostname -m setup查看fact变量,magic变量
  2. 可以使用自定义变量group_vars(存放主机组)和host_vars(存放主机)
[student@workstation file-manage]$ cat test.j2
hello , {{ inventory_hostname }} !
my ip : {{ ansible_default_ipv4['address'] }}

2.3 使用template模块调用JINJA2模板

 [student@workstation file-manage]$ cat mmx.yml
---
- name: 使用jinja2模板文件
  hosts: all
  remote_user: root
  tasks:
          - name: 创建/root/mmx.txt文件
            template:
                    src: test.j2
                    dest: /root/mmx.txt
[student@workstation file-manage]$ ansible all -a 'cat /root/mmx.txt' -u root
serverb.lab.example.com | CHANGED | rc=0 >>
hello , serverb.lab.example.com !
my ip : 172.25.250.11

servera.lab.example.com | CHANGED | rc=0 >>
hello , servera.lab.example.com !
my ip : 172.25.250.10

2.4 使用JINJA2循环

2.4.1 循环格式

# 格式:{% for 变量 in 变量组%}
# {{ 变量 }}
# {% endfor %}

[student@workstation file-manage]$ cat test.j2
hello , {{ inventory_hostname }} !
my ip : {{ ansible_default_ipv4['address'] }}

{% for name in home %}
家庭成员: {{ name }}
{% endfor %}

2.4.2 循环示例

[student@workstation file-manage]$ cat mmx.yml
---
- name: 使用jinja2模板文件
  hosts: all
  vars:
          - home:
                  - xiaoming
                  - xiaohong
                  - xiaobai
                  - xiaoli
  remote_user: root
  tasks:
          - name: 创建/root/mmx.txt文件
            template:
                    src: test.j2
                    dest: /root/mmx.txt
[student@workstation file-manage]$ ansible all -a 'cat /root/mmx.txt' -u root
serverb.lab.example.com | CHANGED | rc=0 >>
hello , serverb.lab.example.com !
my ip : 172.25.250.11

家庭成员: xiaoming
家庭成员: xiaohong
家庭成员: xiaobai
家庭成员: xiaoli

servera.lab.example.com | CHANGED | rc=0 >>
hello , servera.lab.example.com !
my ip : 172.25.250.10

家庭成员: xiaoming
家庭成员: xiaohong
家庭成员: xiaobai
家庭成员: xiaoli

2.5 使用JINJA2用于判断

2.5.1 判断格式

# 使用if+endif开头结尾,中间是条件成立执行
# {% if 条件 %}
# {{ result}}
# {% endif %}
或
# {% if 条件 %}
# {{ result1 }}
# {% else %}
# {{ result2 }}
# {% endif %}

2.5.2 判断示例

# 4>3,所以一定执行第一个if判断,第二个不会执行 
[student@workstation file-manage]$ cat test.j2
{% if 4>3 %}
4大于3
{% else %}
4小于3
{% endif %}

[student@workstation file-manage]$ ansible all -a 'cat /root/mmx.txt' -u root
serverb.lab.example.com | CHANGED | rc=0 >>

4大于3

2.6 变量过滤器

可以通过参数:filter过滤出json格式的变量

[student@workstation file-manage]$ ansible all -m setup -a 'filter="*fqdn*"'
serverb.lab.example.com | SUCCESS => {
    "ansible_facts": {
        "ansible_fqdn": "serverb.lab.example.com",
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false
}
servera.lab.example.com | SUCCESS => {
    "ansible_facts": {
        "ansible_fqdn": "servera.lab.example.com",
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false
}

2.7 JINJA2模板练习

2.7.1 题目说明

  1. 开启实验环境
  2. 创建一个inventory文件,文件包括
[webservers]
servera.lab.example.com

[workstations]
workstation.lab.example.com
  1. 创建一个motd.j2的文件
    1. 使用变量ansible_facts['fqdn'],描述主机完全限定域名
    2. 使用变量ansible_facts['distribution'],描述主机系统
    3. 使用变量ansible_facts['distribution_version'],描述系统版本
    4. 使用变量system_owner,描述系统所有者邮箱
  2. 创建一个motd.yml的文件,使用motd.j2模板,在所有节点设置/etc/motdd文件信息,所有者所属组均为root,权限0644

2.7.2 实验

1、 编辑模板文件

[student@workstation file-template]$ cat motd.j2
主机的FQDN:     {{ ansible_facts['fqdn'] }}
主机系统:       {{ ansible_facts['distribution'] }}
系统版本:       {{ ansible_facts['distribution_version'] }}
系统所有者邮箱: {{ system_owner }}

2、 编辑playbook文件

[student@workstation file-template]$ cat motd.yml
---
- name: 配置SOE
  hosts: all
  remote_user: root
  become: true
  vars:
          - system_owner: mmx@qq.com
  tasks:
          - name: 配置/etc/motd
            template:
                    src: motd.j2
                    dest: /etc/motd
                    owner: root
                    group: root
                    mode: 0644

3、 执行

[student@workstation file-template]$ ansible-playbook motd.yml

PLAY [配置SOE] ********************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************************
ok: [servera.lab.example.com]
ok: [workstation.lab.example.com]

TASK [配置/etc/motd] **************************************************************************************************************************************************************************************************************************************
changed: [servera.lab.example.com]
changed: [workstation.lab.example.com]

PLAY RECAP **********************************************************************************************************************************************************************************************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
workstation.lab.example.com : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

4、 测试查看结果

登录显示系统信息,题目还是蛮有意思的哈!!!

[student@workstation file-template]$ ssh workstation
主机的FQDN:     workstation.lab.example.com
主机系统:       RedHat
系统版本:       8.0
系统所有者邮箱: mmx@qq.com
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sun Aug 14 22:33:28 2022 from 172.25.250.9
[student@workstation ~]$ ssh servera
主机的FQDN:     servera.lab.example.com
主机系统:       RedHat
系统版本:       8.0
系统所有者邮箱: mmx@qq.com
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sun Aug 14 22:33:28 2022 from 172.25.250.9

3、 综合实验

3.1 题目要求

  1. 开启实验
  2. 创建inventory文件,主机组:servers包括主机severb.lab.example.com
  3. 创建motd.j2文件,输出系统总内存和核心数
    1. 使用变量ansible_facts['memtotal_mb']统计系统总内存
    2. 使用变量ansible_processor_count统计系统处理器个数
  4. 创建motd.yml文件
    1. 使用motd.j2模板
    2. 所有者和所有组为root
    3. 权限为0644,输出到/etc/motd
    4. 使用stat和debug模块,检查 /etc/motd的状态并打印出相关信息
    5. 使用copy模块将files/issue放到受管节点/etc目录下,所有者和所有组均为root,权限为0644
    6. 使用file模块确定将/etc/issue.net链接到/etc/issue
  5. 远程连接用户为devops,需要提升权限
  6. 运行playbook,检查语法
  7. 判断成绩,结束实验

3.2 实验

3.2.1 开启实验

[student@workstation ~]$ lab file-review start

Setting up workstation for lab exercise work:

 · The ansible package is installed on workstation.............  SUCCESS
 · Creating the working directory..............................  SUCCESS
 · Downloading ansible.cfg.....................................  SUCCESS
 · Downloading inventory.......................................  SUCCESS
 · Downloading motd.j2.........................................  SUCCESS
 · Downloading issue...........................................  SUCCESS
 · Downloading motd.yml........................................  SUCCESS
 · Changing permissions to the working directory...............  SUCCESS
 · Configuring sshd on serverb.lab.example.com.................  SUCCESS
 · Restarting sshd on serverb.lab.example.com..................  SUCCESS
 · Backing up files on serverb.lab.example.com.................  SUCCESS
[student@workstation ~]$ cd file-review/
[student@workstation file-review]$ cat ansible.cfg
[defaults]
inventory = inventory
ansible_managed = Ansible managed: modified on %Y-%m-%d %H:%M:%S

3.2.2 编辑inventory文件和motd.j2文件

[student@workstation file-review]$ cat inventory
[servers]
serverb.lab.example.com
[student@workstation file-review]$ cat motd.j2
总内存: {{ ansible_facts['memtotal_mb'] }}
核心数: {{ ansible_facts['pocessor_count'] }}

3.2.3 编辑playbook文件

[student@workstation file-review]$ cat motd.yml
---
- name: 综合实验
  hosts: all
  remote_user: devops
  become: yes
  tasks:
          - name: 配置/etc/motd
            template:
                    src: motd.j2
                    dest: /etc/motd
                    owner: root
                    group: root
                    mode: 0644
          - name: 使用copy模块将files/issue放到/etc/issue
            copy:
                    src: files/issue
                    dest: /etc/issue
                    owner: root
                    group: root
                    mode: '0644'
          - name: 使用stat模块+debug检查文件
            stat:
                    path: /etc/issue
            register: issue_msg
          - debug:
                  var: issue_msg

          - name: 删除一个文件
            file:
                    path: /etc/issue.net
                    state: absent
            ignore_errors: yes

          - name: 使用file模块做链接
            file:
                    src: /etc/issue
                    dest: /etc/issue.net
                    state: link

3.2.4 运行playbook

[student@workstation file-review]$ ansible-playbook motd.yml

PLAY [综合实验] ********************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com]

TASK [配置/etc/motd] *************************************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com]

TASK [使用copy模块将files/issue放到/etc/issue] ****************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com]

TASK [使用stat模块+debug检查文件] ******************************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com]

TASK [debug] *******************************************************************************************************************************************************************************************************************
ok: [serverb.lab.example.com] => {
    "issue_msg": {
        "changed": false,
        "failed": false,
        "stat": {
            "atime": 1660493389.059687,
            "attr_flags": "",
            "attributes": [],
            "block_size": 4096,
            "blocks": 8,
            "charset": "us-ascii",
            "checksum": "daa29e8adf76b04b6c747a9d5b87afaa88bcb3be",
            "ctime": 1660493346.003687,
            "dev": 64513,
            "device_type": 0,
            "executable": false,
            "exists": true,
            "gid": 0,
            "gr_name": "root",
            "inode": 4320643,
            "isblk": false,
            "ischr": false,
            "isdir": false,
            "isfifo": false,
            "isgid": false,
            "islnk": false,
            "isreg": true,
            "issock": false,
            "isuid": false,
            "mimetype": "text/plain",
            "mode": "0644",
            "mtime": 1660493345.818687,
            "nlink": 1,
            "path": "/etc/issue",
            "pw_name": "root",
            "readable": true,
            "rgrp": true,
            "roth": true,
            "rusr": true,
            "size": 395,
            "uid": 0,
            "version": "1968508434",
            "wgrp": false,
            "woth": false,
            "writeable": true,
            "wusr": true,
            "xgrp": false,
            "xoth": false,
            "xusr": false
        }
    }
}

TASK [删除一个文件] ******************************************************************************************************************************************************************************************************************
changed: [serverb.lab.example.com]

TASK [使用file模块做链接] *************************************************************************************************************************************************************************************************************
changed: [serverb.lab.example.com]

PLAY RECAP *********************************************************************************************************************************************************************************************************************
serverb.lab.example.com    : ok=7    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

# 测试实验结果
[student@workstation file-review]$ ssh serverb
*------------------------------- PRIVATE SYSTEM -----------------------------*
*   Access to this computer system is restricted to authorised users only.   *
*                                                                            *
*      Customer information is confidential and must not be disclosed.       *
*----------------------------------------------------------------------------*
总内存: 821
核心数: 1

3.2.5 判断成绩

[student@workstation file-review]$ lab file-review grade

Grading the student's work on workstation:

 · Ensuring Ansible inventory file is present..................  PASS
 · Ensuring motd.j2 file is present............................  PASS
 · Ensuring Ansible playbook is present........................  PASS

Grading the student's work on serverb.lab.example.com:

 · Checking motd...............................................  PASS
 · Checking /etc/issue.........................................  PASS
 · Checking /etc/issue.net.....................................  PASS

Overall lab grade..............................................  PASS

# 结束实验
[student@workstation file-review]$ lab file-review grade

Grading the student's work on workstation:

 · Ensuring Ansible inventory file is present..................  PASS
 · Ensuring motd.j2 file is present............................  PASS
 · Ensuring Ansible playbook is present........................  PASS

Grading the student's work on serverb.lab.example.com:

 · Checking motd...............................................  PASS
 · Checking /etc/issue.........................................  PASS
 · Checking /etc/issue.net.....................................  PASS

Overall lab grade..............................................  PASS