Ansible 模块使用

来自CloudWiki
跳转至: 导航搜索

Ansible模块使用

使用ansible命令来运行临时命令:

ansible host-pattern -m module [-a 'module arguments'] [-i inventory]

host-pattern参数用于指定应在其上运行临时命令的受管主机。它可以是清单中的特定受管主机或主机组。您已看到过它与--list-hosts选项结合使用,此选项显示通过特定主机模式匹配的主机。您也了解过可以使用-i选项来指定要使用的其他清单位置,取代当前Ansible配置文件中的默认位置。

-m选项将Ansible应在目标主机上运行的module的名称作为参数。模块是为了实施您的任务而执行的小程序。一些模块不需要额外的信息,但其他模块需要使用额外参数来指定其操作详情。-a选项以带引号字符串形式取这些参数的列表。

-m:要执行的模块,默认为command

-a:指定模块的参数

-u:ssh连接的用户名,默认用root,ansible.cfg中可以配置

-b,--become:变成那个用户身份,不提示密码

-k:提示输入ssh登录密码,当使用密码验证的时候用

-s:sudo运行

  • 大部分模块带有参数,-a 选项传递参数到指定的模块,一些模块可以无参数;一些模块可多参数,参数说明可以在模块文档中找到*
ansible all -m copy -a "src=/root/你好.txt dest=/root"
  • 大部分模块设计为:idempotent(幂等),意味着可以安全多次运行,如果系统已经是正确状态,则模块不会执行任何操作
  • 如果执行临时命令时省略了 -m 选项,Ansible 将参考配置文件里定义的模块
  • 如果未定义模块,Ansible 将使用内部预定义的 command 模块

例如,以下临时命令是等同的:

ansible host-pattern -m command -a 'module arguments’
ansible host-pattern -a 'module arguments'

默认的 command 模块允许管理员对受管主机执行命令。-a 选项后跟需要执行的命令

[root@localhost ~]# ansible 10.0.0.30 -m command -a "ls /root"

10.0.0.30 | CHANGED | rc=0 >>
anaconda-ks.cfg
HelloWorld
HW
Python-3.7.5
Python-3.7.5.tgz
test.sh
welcome

Auto11-1.png

Ansible模块查看

ad hoc 使用模块来完成任务,Ansible 已经提供了上千个模块

ansible-doc -l 命令列出系统中所有模块,ansible-doc 命令还可以显示模块的使用帮助

ansible-doc -l |grep "aws"

[root@localhost ~]# ansible-doc ping

 > ANSIBLE.BUILTIN.PING    (/usr/local/Python3/lib/python3.7/site-packages/ansible/modules/ping.py)

        A trivial test module, this module always returns `pong' on successful contact. It does not make sense in playbooks,
        but it is useful from `/usr/bin/ansible' to verify the ability to login and that a usable Python is configured. This
        is NOT ICMP ping, this is just a trivial test module that requires Python on the remote-node. For Windows targets,
        use the [ansible.windows.win_ping] module instead. For Network targets, use the [ansible.netcommon.net_ping] module
        instead.

OPTIONS (= is mandatory):

- data
        Data to return for the `ping' return value.
        If this parameter is set to `crash', the module will cause an exception.
        [Default: pong]
        type: str


Ansible模块分类

https://blog.csdn.net/Horizon_carry/article/details/108509760

命令模块

主要是command 和 shell 模块

shell模块和command模块的区别:https://qa.1r1g.com/sf/ask/3966433271/

https://blog.51cto.com/u_15076236/3557019

shell – 在目标上执行 shell 命令 ,它几乎与命令模块完全一样,但通过远程节点上的 shell (/bin/sh) 运行命令。

command – 在目标上执行命令,命令将不会通过 shell 处理,所以像 $HOME 这样的变量和像 "<", ">", "|", ";" 这样的操作 和“&”将不起作用。如果您需要这些功能,请使用 shell 模块。

`command` 为您提供更多的安全性(或更所谓的隔离)。换句话说,您的命令执行不受用户环境变量的影响。然而,`shell` 与在终端上执行命令非常相似 (2认同)

例子:

#command模块
ansible all -m command -a "uname"
ansible all -m command -a "cat /etc/hosts"
ansible all -a "date"  
ansible all -m command -a  "......"

#shell模块
ansible all -m shell -a "uname"
ansible all -m shell -a "cat /etc/hosts"
ansible all -m shell -a "free -m | head -2"
ansible all -m shell -a "echo 'Hello World' > ~/test.txt"

文件类模块

有以下模块:

copy:复制

file:设置权限和属性

lineinfile:确认特定行是否在文件中

synchronize:使用rsync同步内容

ansible all -m copy -a "src=/root/你好.txt dest=/root"

ansible slave -m file -a "dest=/root/hello.txt mode=600 owner=flyhorse group=flyhorse"

dest 被操作的文件

mode 管理权限

owner: 属主

group :属组

[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 
3.7.5 (default, May  4 2022, 15:08:24) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]. This feature will be removed from ansible-core in
 version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
192.168.100.20 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 1000,
    "group": "flyhorse",
    "mode": "0600",
    "owner": "flyhorse",
    "path": "/root/hello.txt",
    "secontext": "system_u:object_r:admin_home_t:s0",
    "size": 16,
    "state": "file",
    "uid": 1000
}

文件模块的其他例子:https://www.bbsmax.com/A/MAzAqX6yz9/

软件包模块

package:使用操作系统自动检测软件包管理器管理软件包

yum:使用yum命令安装软件

apt:使用apt命令安装软件

dnf:使用dnf命令安装软件

gem:管理Ruby gem

pip:从pypi管理python软件包

ansible slave -m yum -a "name=httpd state=present" #下载httpd

[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller 
starting with Ansible 2.12. Current version: 3.7.5 (default, May  4 2022, 
15:08:24) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]. This feature will be removed 
from ansible-core in version 2.12. Deprecation warnings can be disabled by setting
 deprecation_warnings=False in ansible.cfg.
192.168.100.20 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "msg": "",
    "rc": 0,
    "results": [
        "httpd-2.4.6-31.el7.centos.x86_64 providing httpd is already installed"
    

ansible test -m yum -a "name=httpd state=absent" #卸载httpd

系统模块

firewalld:使用firewalld管理任意端口和服务

reboot:重启

service:管理服务

user:添加、删除和管理用户账号

service模块

ansible slave -m service -a "name=httpd state=started" #打开httpd服务

ansible slave -m service -a "name=httpd state=restarted" #重启httpd服务

ansible slave -m service -a "name=httpd state=stopped" #停止httpd服务

[root@localhost ansible_test]# ansible slave -m service -a "name=httpd state=started"

 [DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller 
starting with Ansible 2.12. Current version: 3.7.5 (default, May  4 2022, 
15:08:24) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]. This feature will be removed 
from ansible-core in version 2.12. Deprecation warnings can be disabled by setting
 deprecation_warnings=False in ansible.cfg.
192.168.100.20 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "name": "httpd",
    "state": "started",
    "status": {
        "ActiveEnterTimestampMonotonic": "0",
        "ActiveExitTimestampMonotonic": "0",
        "ActiveState": "inactive",
        "After": "tmp.mount systemd-journald.socket basic.target network.target nss-lookup.target system.slice remote-fs.target -.mount",
        "AllowIsolate": "no",
        "AmbientCapabilities": "0",
        "AssertResult": "no",
        "AssertTimestampMonotonic": "0",
        "Before": "shutdown.target",
        "BlockIOAccounting": "no",
        "BlockIOWeight": "18446744073709551615",
        "CPUAccounting": "no",
        "CPUQuotaPerSecUSec": "infinity",
        "CPUSchedulingPolicy": "0",
        "CPUSchedulingPriority": "0",
        "CPUSchedulingResetOnFork": "no",
        "CPUShares": "18446744073709551615",
        "CanIsolate": "no",
        "CanReload": "yes",
        "CanStart": "yes",
        "CanStop": "yes",
        "CapabilityBoundingSet": "18446744073709551615",
        "CollectMode": "inactive",
        "ConditionResult": "no",
        "ConditionTimestampMonotonic": "0",
        "Conflicts": "shutdown.target",
        "ControlPID": "0",
        "DefaultDependencies": "yes",
        "Delegate": "no",
        "Description": "The Apache HTTP Server",
        "DevicePolicy": "auto",
        "EnvironmentFile": "/etc/sysconfig/httpd (ignore_errors=no)",
        "ExecMainCode": "0",
        "ExecMainExitTimestampMonotonic": "0",
        "ExecMainPID": "0",
        "ExecMainStartTimestampMonotonic": "0",
        "ExecMainStatus": "0",
        "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStop": "{ path=/bin/kill ; argv[]=/bin/kill -WINCH ${MAINPID} ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "FailureAction": "none",
        "FileDescriptorStoreMax": "0",
        "FragmentPath": "/usr/lib/systemd/system/httpd.service",
        "GuessMainPID": "yes",
        "IOScheduling": "0",
        "Id": "httpd.service",
        "IgnoreOnIsolate": "no",
        "IgnoreOnSnapshot": "no",
        "IgnoreSIGPIPE": "yes",
        "InactiveEnterTimestampMonotonic": "0",
        "InactiveExitTimestampMonotonic": "0",
        "JobTimeoutAction": "none",
        "JobTimeoutUSec": "0",
        "KillMode": "control-group",
        "KillSignal": "18",
        "LimitAS": "18446744073709551615",
        "LimitCORE": "18446744073709551615",
        "LimitCPU": "18446744073709551615",
        "LimitDATA": "18446744073709551615",
        "LimitFSIZE": "18446744073709551615",
        "LimitLOCKS": "18446744073709551615",
        "LimitMEMLOCK": "65536",
        "LimitMSGQUEUE": "819200",
        "LimitNICE": "0",
        "LimitNOFILE": "4096",
        "LimitNPROC": "7897",
        "LimitRSS": "18446744073709551615",
        "LimitRTPRIO": "0",
        "LimitRTTIME": "18446744073709551615",
        "LimitSIGPENDING": "7897",
        "LimitSTACK": "18446744073709551615",
        "LoadState": "loaded",
        "MainPID": "0",
        "MemoryAccounting": "no",
        "MemoryCurrent": "18446744073709551615",
        "MemoryLimit": "18446744073709551615",
        "MountFlags": "0",
        "Names": "httpd.service",
        "NeedDaemonReload": "no",
        "Nice": "0",
        "NoNewPrivileges": "no",
        "NonBlocking": "no",
        "NotifyAccess": "main",
        "OOMScoreAdjust": "0",
        "OnFailureJobMode": "replace",
        "PermissionsStartOnly": "no",
        "PrivateDevices": "no",
        "PrivateNetwork": "no",
        "PrivateTmp": "yes",
        "ProtectHome": "no",
        "ProtectSystem": "no",
        "RefuseManualStart": "no",
        "RefuseManualStop": "no",
        "RemainAfterExit": "no",
        "Requires": "system.slice basic.target -.mount",
        "RequiresMountsFor": "/var/tmp",
        "Restart": "no",
        "RestartUSec": "100ms",
        "Result": "success",
        "RootDirectoryStartOnly": "no",
        "RuntimeDirectoryMode": "0755",
        "SameProcessGroup": "no",
        "SecureBits": "0",
        "SendSIGHUP": "no",
        "SendSIGKILL": "yes",
        "Slice": "system.slice",
        "StandardError": "inherit",
        "StandardInput": "null",
        "StandardOutput": "journal",
        "StartLimitAction": "none",
        "StartLimitBurst": "5",
        "StartLimitInterval": "10000000",
        "StartupBlockIOWeight": "18446744073709551615",
        "StartupCPUShares": "18446744073709551615",
        "StatusErrno": "0",
        "StopWhenUnneeded": "no",
        "SubState": "dead",
        "SyslogLevelPrefix": "yes",
        "SyslogPriority": "30",
        "SystemCallErrorNumber": "0",
        "TTYReset": "no",
        "TTYVHangup": "no",
        "TTYVTDisallocate": "no",
        "TasksAccounting": "no",
        "TasksCurrent": "18446744073709551615",
        "TasksMax": "18446744073709551615",
        "TimeoutStartUSec": "1min 30s",
        "TimeoutStopUSec": "1min 30s",
        "TimerSlackNSec": "50000",
        "Transient": "no",
        "Type": "notify",
        "UMask": "0022",
        "UnitFilePreset": "disabled",
        "UnitFileState": "disabled",
        "WatchdogTimestampMonotonic": "0",
        "WatchdogUSec": "0"
    }
}

user模块

ansible slave -m user -a "name=Horizon_carry password=000000"


[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller 
starting with Ansible 2.12. Current version: 3.7.5 (default, May  4 2022, 
15:08:24) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]. This feature will be removed 
from ansible-core in version 2.12. Deprecation warnings can be disabled by setting
 deprecation_warnings=False in ansible.cfg.
[WARNING]: The input password appears not to have been hashed. The 'password'
argument must be encrypted for this module to work properly.
192.168.100.20 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1001,
    "home": "/home/Horizon_carry",
    "name": "Horizon_carry",
    "password": "NOT_LOGGING_PASSWORD",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1001
}

验证:在受管主机输入cat /etc/passwd 可以查看增加的用户。

ansible web -m user -a 'name=test01 shell=/bin/bash home=/opt/test01 state=present'

删除用户:

[root@manage01 ~]# ansible -m user 192.168.98.201 -a "name=baishuming1 state=absent remove=yes"

192.168.98.201 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "force": false, 
    "name": "baishuming1", 
    "remove": true, 
    "state": "absent"
}

链接:https://cloud.tencent.com/developer/article/2108737

firewalld模块

firewalld模块用来添加、删除防火墙规则。

常用变量 service : 指定放行的服务,此服务必须要在firewall-cmd --get-services查询的到。

permanent : 保存策略,下次启动的时候自动加载。

state : 指定防火墙策略状态,enable表示策略生效,disable表示策略禁用,present新建策略,absent删除策略。

port :指定放行的端口/协议。

zone : 指定防火墙信任级别。

drop: 丢弃所有进入的包,而不给出任何响应
block: 拒绝所有外部发起的连接,允许内部发起的连接
public: 允许指定的进入连接
external: 同上,对伪装的进入连接,一般用于路由转发
dmz: 允许受限制的进入连接
work: 允许受信任的计算机被限制的进入连接,类似 workgroup
home: 同上,类似 homegroup
internal: 同上,范围针对所有互联网用户
trusted: 信任所有连接

链接:https://blog.csdn.net/m0_46305762/article/details/107160317

案例1:新增放行https协议数据的策略,下次重启的时候策略自动加载

[root@control ~]# ansible node1 -m firewalld -a 'service=https permanent=yes state=enabled'

node1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": true,
    "msg": "Permanent operation, Changed service https to enabled"
}

策略没有立马生效:ansible node1 -a 'firewall-cmd --zone=public --list-all'

重启防火墙服务:ansible node1 -m service -a 'name=firewalld state=restarted'

防火墙策略生效:ansible node1 -a 'firewall-cmd --zone=public --list-all'

案例2:在默认信任级别新增放行tcp 8081端口的策略且策略状态为禁用,下次重启的时候策略自动加载

[root@control ~]# ansible node1 -m firewalld -a 'port=8081/tcp permanent=yes state=disabled'

node1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "msg": "Permanent operation"
}

重启防火墙策略:ansible node1 -m service -a 'name=firewalld state=restarted'

策略未启用:ansible node1 -a 'firewall-cmd --zone=public --list-all'

网络工具模块

get_url:通过http、https或者ftp下载文件

nmcli:管理网络

uri:与web服务交互

Ansible的get_url模块主要用于实现被控客户端从远程将文件下载到本地。该模块有四个常用参数,url参数主要用于指定被控客户端要远程下载的文件,dest参数主要指定目的文件夹,mode参数指定下载后的文件权限,force参数可以为yes或者是no。如果force参数为yes,则表示如果所下载的内容和原目录下的文件内容不一样,则下载并替换原文件,如果相同,则不进行下载;如果force参数为no,则不管目录下的同名文件是否相同,只有在目标不存在时才下载文件。在Ansible0.6版本之前,该参数默认为yes,在Ansible0.6之后,该参数默认为no。在生产环境中,一般小文件的下载选用yes。 该模块使用如下:

ansible exp -m get_url -a "url=http://nginx.org/download/nginx-1.4.7.tar.gz dest=/root/ mode=0644 force=yes"

链接:https://blog.csdn.net/rqaz123/article/details/126383008

应用举例

示例:ansible 使用command命令

ansible all -m command -a "ps -aux"

ansible all -m command -a "ps -aux"

ansible all -m command -a "ps -aux|grep 5000"

远程重启:

ansible room306 -m command -a "sudo reboot"

[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 
3.6.8 (default, Sep 10 2021, 09:13:53) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)]. This feature will be removed from ansible-core in 
version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
119.3.212.154 | FAILED | rc=-1 >>
Failed to connect to the host via ssh: ssh: connect to host 119.3.212.154 port 22: Connection refused

远程关机:

[root@ecs-c031 ~]# ansible room306 -m command -a "shutdown -h now"

[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 
3.6.8 (default, Sep 10 2021, 09:13:53) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)]. This feature will be removed from ansible-core in 
version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
119.3.212.154 | FAILED | rc=-1 >>
Failed to connect to the host via ssh: ssh: connect to host 119.3.212.154 port 22: Connection refused

示例:ansible 使用shell命令

ansible all -m shell -a "ps -aux | grep 5000"

localhost | CHANGED | rc=0 >>
root       2325 61.0  2.0 355184 41216 pts/0    Rl+  12:23   0:00 /usr/local/Python3/bin/python3.7 /usr/local/Python3/bin/ansible all -m shell -a ps -aux | grep 5000
root       2329  0.0  1.8 356180 38328 pts/0    S+   12:23   0:00 /usr/local/Python3/bin/python3.7 /usr/local/Python3/bin/ansible all -m shell -a ps -aux | grep 5000
root       2330  0.0  1.8 362208 38212 pts/0    S+   12:23   0:00 /usr/local/Python3/bin/python3.7 /usr/local/Python3/bin/ansible all -m shell -a ps -aux | grep 5000
root       2331  0.0  1.8 362212 38212 pts/0    R+   12:23   0:00 /usr/local/Python3/bin/python3.7 /usr/local/Python3/bin/ansible all -m shell -a ps -aux | grep 5000
root       2434  0.0  0.0 113184  1192 pts/0    S+   12:23   0:00 /bin/sh -c ps -aux | grep 5000
root       2437  0.0  0.0 113184   188 pts/0    R+   12:23   0:00 /bin/sh -c ps -aux | grep 5000
10.0.0.30 | CHANGED | rc=0 >>

command 和shell的区别:

https://www.cnblogs.com/fanren224/p/8525477.html

示例:Ansible 收集远程主机信息

setup 模块用于收集远程主机的一些基本信息。

filter参数:用于进行条件过滤。如果设置,仅返回匹配过滤条件的信息。

ansible slave -m setup

ansible slave -m setup -a "filter=ansible_memory_mb"

10.0.0.20 | SUCCESS => {
    "ansible_facts": {
        "ansible_memory_mb": {
            "nocache": {
                "free": 1762,
                "used": 218
            },
            "real": {
                "free": 1652,
                "total": 1980,
                "used": 328
            },
            "swap": {
                "cached": 0,
                "free": 2047,
                "total": 2047,
                "used": 0
            }
        },
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}

其他常用信息:

ansible_all_ipv4_addresses:仅显示ipv4的信息。
ansible_devices:仅显示磁盘设备信息。
ansible_distribution:显示是什么系统,例:centos,suse等。
ansible_distribution_major_version:显示是系统主版本。
ansible_distribution_version:仅显示系统版本。
ansible_machine:显示系统类型,例:32位,还是64位。
ansible_eth0:仅显示eth0的信息。
ansible_hostname:仅显示主机名。
ansible_kernel:仅显示内核版本。
ansible_lvm:显示lvm相关信息。
ansible_memtotal_mb:显示系统总内存。
ansible_memfree_mb:显示可用系统内存。
ansible_memory_mb:详细显示内存情况。
ansible_swaptotal_mb:显示总的swap内存。
ansible_swapfree_mb:显示swap内存的可用内存。
ansible_mounts:显示系统磁盘挂载情况。
ansible_processor:显示cpu个数(具体显示每个cpu的型号)。
ansible_processor_vcpus:显示cpu个数(只显示总的个数)。

原文链接:https://blog.csdn.net/dylloveyou/article/details/81951679

示例:Ansible上传文件

将一个文本文件上传至远程主机的用户home目录中,上传之前先查看远程主机上用户home目录上的文件。

[root@localhost ~]# ansible all -m shell -a "ls ~/*.*"

[root@localhost ~]# ansible all -m copy -a "src=/root/你好.txt dest=/root"

localhost | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/root/你好.txt",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "path": "/root/你好.txt",
    "size": 0,
    "state": "file",
    "uid": 0
}

可以看出,文件已经传输至另外两台主机,本例中的localhost本就有"你好.txt",默认不会被覆盖,因此changed是false。我们再执行ls命令进行验证一下。

ansible slave -m shell -a "ls ~/*.*"

拓展:fetch模块,从远程主机获取文件

ansible slave -m fetch -a "src=/etc/fstab dest=./ma"

示例:Ansible运行python脚本

ansible all -m copy -a "src=/root/test.py dest=/root"

ansible all -m shell -a "python3 /root/test.py"

localhost | CHANGED | rc=0 >>
hello world
10.0.0.30 | CHANGED | rc=0 >>
hello world
10.0.0.40 | CHANGED | rc=0 >>
hello world