[Ansible小贴士] 支撑nyanshible的技术 [cowsay/回调插件]

在与Ansible社区的人交谈时,我不经意地使用了词语“にゃんしぶる”,这导致了可能会生成一些在Ansible执行时出现猫咪的模块的讨论,所以我暂时试着在消息输出中实现“にゃんしぶる”,看看是否可行。

2019-11-04_14h05_26.png

环境 –

[zaki@manager nyan]$ ansible --version
ansible 2.8.5
  config file = /home/zaki/work/ansible/nyan/ansible.cfg
  configured module search path = [u'/home/zaki/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Aug  7 2019, 00:51:29) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
[zaki@manager nyan]$
[zaki@manager nyan]$ cat /etc/redhat-release
CentOS Linux release 7.7.1908 (Core)
[zaki@manager nyan]$

另外,本文内容包括了cowsay的使用方法和修改方式,以及Ansible回调插件的简单构建方法。

我想试试,不要解释。

详细的内容稍后提到,只需要将以下脚本复制粘贴到/usr/local/bin/cowsay。(该脚本将根据参数对ASCII艺术进行整理并打印出来)

#!/usr/bin/perl

my $border = "-" x ((length $ARGV[-1]) + 2);
print <<"__EOL__";
 $border
< $ARGV[-1] >
 $border
 \\ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ
__EOL__

给 /usr/local/bin/cowsay 增加执行权限 chmod 755 ,然后执行 Ansible 即可。

如果想要恢复原状,只需删除/usr/local/bin/cowsay即可。

牛说:“にゃんしぶる”

如果在Ansible执行时系统上安装了cowsay命令,则具备使用此命令输出任务名称的功能。

这部分是调用Ansible来使用cowsay的部分。

准备好自己的 cowsay 并进行尝试

在执行Ansible时,检查是否安装了cowsay是根据以下列表中是否存在执行文件来判断的。

b_COW_PATHS = (
    b"/usr/bin/cowsay",
    b"/usr/games/cowsay",
    b"/usr/local/bin/cowsay",  # BSD path for cowsay
    b"/opt/local/bin/cowsay",  # MacPorts path for cowsay
)

如果你在这些路径中放置了可执行的cowsay脚本,那就没问题了。换句话说,只有当执行Ansible时,路径(如~/local/bin/cowsay)中有cowsay存在,才会有反应。

所以,即使没有通过yum等安装cowsay,只要自行准备/usr/local/bin/cowsay,就可以在Ansible中使用nyanshible。
(※不要忘记使用chmod 755给予执行权限)

实施案例

比如说Perl脚本

#!/usr/bin/perl

my $border = "-" x ((length $ARGV[-1]) + 2);
print <<"__EOL__";
 $border
< $ARGV[-1] >
 $border
 \\ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ
__EOL__

如果已安装原始的cowsay,并且没有进行其他特殊配置,则在Ansible中使用cowsay -W 60 -f default “消息”的格式会执行cowsay。

    def banner_cowsay(self, msg, color=None):
        if u": [" in msg:
            msg = msg.replace(u"[", u"")
            if msg.endswith(u"]"):
                msg = msg[:-1]
        runcmd = [self.b_cowsay, b"-W", b"60"]
        if self.noncow:
            thecow = self.noncow
            if thecow == 'random':
                thecow = random.choice(list(self.cows_available))
            runcmd.append(b'-f')
            runcmd.append(to_bytes(thecow))
        runcmd.append(to_bytes(msg))
        cmd = subprocess.Popen(runcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (out, err) = cmd.communicate()
        self.display(u"%s\n" % to_text(out), color=color)

这个函数的

        runcmd = [self.b_cowsay, b"-W", b"60"]

[...]

            runcmd.append(b'-f')
            runcmd.append(to_bytes(thecow))

這一部分的內容。

所以,如果你想要创建自己的cowsay,你可以根据这个调用规范,取出最后一个参数并输出,这样就可以执行nyanshible了。
(在Perl中,脚本的参数可以通过@ARGV数组接收,要引用数组的末尾,可以使用数组索引-1来取出)

执行示例 lì)

[zaki@manager nyan]$ ansible-playbook playbook.yml 
 [WARNING]: provided hosts list is empty, only localhost is available. Note
that the implicit localhost does not match 'all'

 ------------------
< PLAY [localhost] >
 ------------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

 ------------------------
< TASK [Gathering Facts] >
 ------------------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

ok: [localhost]
 -------------
< TASK [ping] >
 -------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

ok: [localhost]
 ------------
< PLAY RECAP >
 ------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[zaki@manager nyan]$

在 cowsay 中添加自定义模板并显示 “喵”

可以通过安装cowsay包并准备cowsay的输出模板来实现nyan式的效果。cowsay默认显示信息为牛的ASCII艺术,但是有多个样本显示格式,并且可以通过选项来改变显示方式。

女性工程师通过cowsay来提升女性魅力 – Qiita

咕噜牛说的设置

[zaki@manager nyan]$ cowsay Ansible
 _________
< Ansible >
 ---------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

可供选择的模板

[zaki@manager nyan]$ cowsay -l
Cow files in /usr/share/cowsay:
beavis.zen blowfish bong bud-frogs bunny cheese cower default dragon
dragon-and-cow elephant elephant-in-snake eyes flaming-sheep ghostbusters
head-in hellokitty kiss kitty koala kosh luke-koala mech-and-cow meow milk
moofasa moose mutilated ren satanic sheep skeleton small sodomized
stegosaurus stimpy supermilker surgery telebears three-eyes turkey turtle
tux udder vader vader-koala www
[zaki@manager nyan]$ 

我来试着选择Tux

[zaki@manager nyan]$ cowsay -f tux Ansible
 _________
< Ansible >
 ---------
   \
    \
        .--.
       |o_o |
       |:_/ |
      //   \ \
     (|     | )
    /'\_   _/`\
    \___)=(___/

[zaki@manager nyan]$

那么这个模板在哪里呢?如同列表中的第一行“/usr/share/cowsay中的Cow文件”,它位于/usr/share/cowsay目录下。

[zaki@manager nyan]$ ls -F /usr/share/cowsay/
DragonAndCow.pm  default.cow            luke-koala.cow    stimpy.cow
Example.pm       dragon-and-cow.cow     mech-and-cow.cow  supermilker.cow
Frogs.pm         dragon.cow             meow.cow          surgery.cow
MechAndCow.pm    elephant-in-snake.cow  milk.cow          telebears.cow
Stegosaurus.pm   elephant.cow           moofasa.cow       three-eyes.cow
TextBalloon.pm   eyes.cow               moose.cow         turkey.cow
TuxStab.pm       flaming-sheep.cow      mutilated.cow     turtle.cow
beavis.zen.cow   ghostbusters.cow       ren.cow           tux.cow
blowfish.cow     head-in.cow            satanic.cow       udder.cow
bong.cow         hellokitty.cow         sheep.cow         vader-koala.cow
bud-frogs.cow    kiss.cow               skeleton.cow      vader.cow
bunny.cow        kitty.cow              small.cow         www.cow
cheese.cow       koala.cow              sodomized.cow
cower.cow        kosh.cow               stegosaurus.cow

这是一个Perl脚本,将AA设置为$the_cow变量的输出内容。

##
## TuX
## (c) pborys@p-soft.silesia.linux.org.pl
##
$the_cow = <<EOC;
   $thoughts
    $thoughts
        .--.
       |o_o |
       |:_/ |
      //   \\ \\
     (|     | )
    /'\\_   _/`\\
    \\___)=(___/

EOC
[zaki@manager nyan]$

那你明白了吗?

##
## A cow wadvertising the World Wide Web, from lim@csua.berkeley.edu
##
$the_cow = <<EOC;
 \\ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ
EOC

順便提一下,如果包含日语(多字节字符),使用UTF-8会出现乱码的情况,所以保存为UTF-16 LE格式。

[zaki@manager ~]$ cowsay -l
Cow files in /usr/share/cowsay:
beavis.zen blowfish bong bud-frogs bunny cat cheese cower default dragon
dragon-and-cow elephant elephant-in-snake eyes flaming-sheep ghostbusters
head-in hellokitty kiss kitty koala kosh luke-koala mech-and-cow meow milk
moofasa moose mutilated ren satanic sheep skeleton small sodomized
stegosaurus stimpy supermilker surgery telebears three-eyes turkey turtle
tux udder vader vader-koala www

只需要一个选择:↑添加了cat。
选择cat并执行。

[zaki@manager ~]$ cowsay -f cat Ansible
 _________
< Ansible >
 ---------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ
[zaki@manager ~]$

如果因为宗教原因等无法在/usr/share/cowsay作为文件的安装位置,那么可以将文件存储在/usr/local/share/cowsay/cat.cow,并在运行时通过环境变量COWPATH指定使用。(详细信息请参考man或cowsay的源代码)

[zaki@manager ~]$ COWPATH=/usr/local/share/cowsay cowsay -l
Cow files in /usr/local/share/cowsay:
cat
[zaki@manager ~]$ COWPATH=/usr/local/share/cowsay cowsay -f cat aaa
 _____
< aaa >
 -----
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ
[zaki@manager ~]$

多种路径设置

[zaki@manager ~]$ COWPATH=/usr/local/share/cowsay:/usr/share/cowsay cowsay -l
Cow files in /usr/local/share/cowsay:
cat
Cow files in /usr/share/cowsay:
beavis.zen blowfish bong bud-frogs bunny cheese cower default dragon
dragon-and-cow elephant elephant-in-snake eyes flaming-sheep ghostbusters
head-in hellokitty kiss kitty koala kosh luke-koala mech-and-cow meow milk
moofasa moose mutilated ren satanic sheep skeleton small sodomized
stegosaurus stimpy supermilker surgery telebears three-eyes turkey turtle
tux udder vader vader-koala www
[zaki@manager ~]$

Ansible配置

由于已经准备好使用自定义模板在cowsay中输出消息,所以要进行从Ansible中选择指定模板的设置。
不过,只需在ansible.cfg文件中进行描述即可。
(如果自定义模板不在默认的/usr/local/share/cowsay等位置,需要添加COWPATH=/usr/local/share/cowsay)

答案: ANSIBLE_COW_SELECTION 的选择

[defaults]
cow_selection = cat

顺便说一下,如果在这里写下像tux这样的,通过cowsay -l命令显示的列表项目,则Ansible的输出将根据该内容进行。另外,如果选择random,则每次输出都会从可用模板中随机选择一个。

执行例子

[zaki@manager nyan]$ ansible-playbook playbook.yml
 [WARNING]: provided hosts list is empty, only localhost is available. Note
that the implicit localhost does not match 'all'

 __________________
< PLAY [localhost] >
 ------------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

 ________________________
< TASK [Gathering Facts] >
 ------------------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

ok: [localhost]
 _____________
< TASK [ping] >
 -------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

ok: [localhost]
 ____________
< PLAY RECAP >
 ------------
 \ ∧_∧  
 .ミ,,・_・ミ
ヾ(,_uuノ

localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[zaki@manager nyan]$

禁用cowsay

[defaults]
nocows = True

或者可以使用yum erase cowsay来删除它。

回调插件用于猫咪模拟。

使用cowsay到目前为止,只是输出了play名和task名,所以无法像正常时一样可爱的猫咪,错误时毛发竖起的猫咪之类的事情。

如果要处理这个区域,可以使用回调插件(平时可以用”默认的错误不好看呀,哦对了,使用yaml的stdout_callback会变得好看”这种东西)来实现。

我將自己創建一個用於「Nyanshiburu的callback plugin」的插件,但只需要基於「預設的callback plugin,當未進行任何設定時使用」來進行開發,如果只是修改顯示的程度,則可以輕鬆地通過複製粘貼完成。

使用Ansible的回调插件来表达突然死亡(echo-sd) – Qiita

回调插件与Ansible本身一样,都是使用Python编写的。对于在CentOS7上通过epel安装的ansible 2.8版本,插件的源代码位于/usr/lib/python2.7/site-packages/ansible/plugins/callback/*.py目录下。

制作一个“にゃんしぶる”回调插件。

首先,在playbook所在的目录中创建一个callback_plugins子目录,并将default.py复制到该目录下并重命名为nyansible.py。

[zaki@manager nyan]$ mkdir callback_plugins/
[zaki@manager nyan]$ cp /usr/lib/python2.7/site-packages/ansible/plugins/callba
ck/default.py callback_plugins/nyansible.py
[zaki@manager nyan]$ ll callback_plugins/
合計 20
-rw-r--r--. 1 zaki zaki 17488 11月  4 12:36 nyansible.py
[zaki@manager nyan]$

以默认设置为基础的最简单的复制粘贴代码

只要更改默认回调插件的显示部分消息,就可以了。
不过,直接写入日语(多字节码)会导致乱码,所以需要进行相应的处理。

中文版暂不提供日语支持。

ERROR! Unexpected Exception, this is probably a bug: Non-ASCII character '\xce' in file /home/zaki/work/ansible/nyan/callback_plugins/nyansible.py on line 89, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details (nyansible.py, line 89)

如果要在脚本中使用日语,需要在脚本的第一行(通常,对于普通的Python脚本,shebang(#!/usr/bin/env python3之类的指令)在第一行,如果有的话就在第二行)中指定编码。
如果使用UTF-8编码的话,

# -*- coding: utf-8 -*-

写。

 [WARNING]: Failure using method (v2_runner_on_ok) in callback plugin
(<ansible.plugins.callback.nyansible.CallbackModule object at 0x7fda2cacc410>):
'ascii' codec can't decode byte 0xe0 in position 0: ordinal not in range(128)

另外,在使用日语时,需要添加表示Unicode编码的u” … “的描述。(添加u前缀以显示ok和changed的消息输出)

self._display.display("fatal: [%s]: FAILED! => %s" ...

在中文中,可以将其翻译为“用于”。

self._display.display(u"エラー: [%s]: FAILED! => %s" ...

写成中文。

修改点示例

[zaki@manager nyan]$ diff -u /usr/lib/python2.7/site-packages/ansible/plugins/c
allback/default.py callback_plugins/nyansible.py
--- /usr/lib/python2.7/site-packages/ansible/plugins/callback/default.py       2019-09-13 06:12:55.000000000 +0900
+++ callback_plugins/nyansible.py       2019-11-04 12:59:50.520091324 +0900
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
 # (c) 2017 Ansible Project
 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -48,7 +49,7 @@

     CALLBACK_VERSION = 2.0
     CALLBACK_TYPE = 'stdout'
-    CALLBACK_NAME = 'default'
+    CALLBACK_NAME = 'nyansible'

     def __init__(self):

@@ -86,11 +87,11 @@

         else:
             if delegated_vars:
-                self._display.display("fatal: [%s -> %s]: FAILED! => %s" % (result._host.get_name(), delegated_vars['ansible_host'],
+                self._display.display(u"Σ(;Φ ω Φ): [%s -> %s]: FAILED! => %s" % (result._host.get_name(), delegated_vars['ansible_host'],
                                                                             self._dump_results(result._result)),
                                       color=C.COLOR_ERROR, stderr=self.display_failed_stderr)
             else:
-                self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)),
+                self._display.display(u"Σ(;Φ ω Φ): [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)),
                                       color=C.COLOR_ERROR, stderr=self.display_failed_stderr)

         if ignore_errors:
@@ -107,9 +108,9 @@
                 self._print_task_banner(result._task)

             if delegated_vars:
-                msg = "changed: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
+                msg = u"ฅ/ᐠ。ᆽ。ᐟ \: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
             else:
-                msg = "changed: [%s]" % result._host.get_name()
+                msg = u"ฅ/ᐠ。ᆽ。ᐟ \: [%s]" % result._host.get_name()
             color = C.COLOR_CHANGED
         else:
             if not self.display_ok_hosts:
@@ -119,9 +120,9 @@
                 self._print_task_banner(result._task)

             if delegated_vars:
-                msg = "ok: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
+                msg = u"ฅ(^・ω・^ฅ): [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
             else:
-                msg = "ok: [%s]" % result._host.get_name()
+                msg = u"ฅ(^・ω・^ฅ): [%s]" % result._host.get_name()
             color = C.COLOR_OK

         self._handle_warnings(result._result)
[zaki@manager nyan]$

运行示例

---
- hosts: localhost
  tasks:
    - ping:
    - shell: date
    - yum:
        name: ruby
[zaki@manager nyan]$ ansible-playbook playbook.yml  
 [WARNING]: provided hosts list is empty, only localhost is available. Note
that the implicit localhost does not match 'all'


PLAY [localhost] ***************************************************************

TASK [Gathering Facts] *********************************************************
ฅ(^・ω・^ฅ): [localhost]

TASK [ping] ********************************************************************
ฅ(^・ω・^ฅ): [localhost]

TASK [shell] *******************************************************************
ฅ/ᐠ。ᆽ。ᐟ \: [localhost]

TASK [yum] *********************************************************************
Σ(;Φ ω Φ): [localhost]: FAILED! => {"changed": false, "changes": {"installed": ["ruby"]}, "msg": "You need to be root to perform this command.\n", "rc": 1, "results": ["Loaded plugins: fastestmirror\n"]}

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

[zaki@manager nyan]$

继承了易于查看错误的YAML插件的”Nyan”框架

不使用默认插件,而是基于一个能将错误消息以yaml格式输出的yaml插件进行构建。
yaml插件的源代码位于/usr/lib/python2.7/site-packages/ansible/plugins/callback/yaml.py。然而,即使查看该源代码,也无法找到输出”ok”、”changed”和”fatal”的代码。
那么它是如何实现的呢?Ansible的回调插件需要创建一个CallbackModule类,而yaml插件是基于默认插件进行继承开发的。
也就是说,基本处理都直接使用了默认插件的处理方式,只是输出处理部分使用了yaml.dump()函数进行实现。

        if abridged_result:
            dumped += '\n'
            dumped += to_text(yaml.dump(abridged_result, allow_unicode=True, width=1000, Dumper=AnsibleDumper, default_flow_style=False))

所以,我们可以通过继承YAML插件并重写输出”ok/changed/fatal”等内容的方法来实现。

创建一个继承yaml插件类的CallbackModule类,用于nyanshible。

首先创建一个只继承了yaml插件而没有实现任何自定义处理的类。就像这样。

我取名叫nyaml。

# -*- coding: utf-8 -*-

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = '''
    callback: nyaml
    type: stdout
    short_description: nyaml-ized Ansible screen output
'''

from ansible.plugins.callback.yaml import CallbackModule as CallbackModule_yaml

class CallbackModule(CallbackModule_yaml):

    """
    nyansible and nyaml /ᐠ。ꞈ。ᐟ\
    """

    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'yaml'

在这个时候,如果在ansible.cfg中写入stdout_callback = nyaml(与普通的yaml插件一样),它就会工作。

实现消息输出处理

从默认插件的源代码中借用消息输出处理函数。
目标函数包括v2_runner_on_ok()和v2_runner_on_failed()等。
首先复制并尝试运行这些函数。

然后,会输出一些警告信息,提示缺少一些定义。

TASK [Gathering Facts] ********************************************************* [WARNING]: Failure using method (v2_runner_on_ok) in callback plugin
(<ansible.plugins.callback.nyaml.CallbackModule object at 0x7fc66f796c50>):
global name 'TaskInclude' is not defined


TASK [ping] ********************************************************************
TASK [shell] *******************************************************************
TASK [yum] ********************************************************************* [WARNING]: Failure using method (v2_runner_on_failed) in callback plugin
(<ansible.plugins.callback.nyaml.CallbackModule object at 0x7fc66f796c50>):
global name 'C' is not defined

据称,任务中没有包括Include和C。

因此,我們從默認插件的代碼中再次借用這個功能(基本上是這個操作的循環)。
所以,整個代碼的結構大致如此。

# -*- coding: utf-8 -*-

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = '''
    callback: nyaml
    type: stdout
    short_description: nyaml-ized Ansible screen output
'''

from ansible.plugins.callback.yaml import CallbackModule as CallbackModule_yaml
from ansible import constants as C
from ansible.playbook.task_include import TaskInclude
from ansible.utils.color import colorize, hostcolor

class CallbackModule(CallbackModule_yaml):

    """
    nyansible and nyaml /ᐠ。ꞈ。ᐟ\
    """

    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'yaml'

    def v2_runner_on_failed(self, result, ignore_errors=False):

        delegated_vars = result._result.get('_ansible_delegated_vars', None)
        self._clean_results(result._result, result._task.action)

        if self._last_task_banner != result._task._uuid:
            self._print_task_banner(result._task)

        self._handle_exception(result._result, use_stderr=self.display_failed_stderr)
        self._handle_warnings(result._result)

        if result._task.loop and 'results' in result._result:
            self._process_items(result)

        else:
            if delegated_vars:
                self._display.display(u"Σ(;Φ ω Φ): [%s -> %s]: FAILED! => %s" % (result._host.get_name(), delegated_vars['ansible_host'],
                                                                            self._dump_results(result._result)),
                                      color=C.COLOR_ERROR, stderr=self.display_failed_stderr)
            else:
                self._display.display(u"Σ(;Φ ω Φ): [%s]: FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result)),
                                      color=C.COLOR_ERROR, stderr=self.display_failed_stderr)

        if ignore_errors:
            self._display.display("...ignoring", color=C.COLOR_SKIP)

    def v2_runner_on_ok(self, result):

        delegated_vars = result._result.get('_ansible_delegated_vars', None)

        if isinstance(result._task, TaskInclude):
            return
        elif result._result.get('changed', False):
            if self._last_task_banner != result._task._uuid:
                self._print_task_banner(result._task)

            if delegated_vars:
                msg = u"ฅ/ᐠ。ᆽ。ᐟ\: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
            else:
                msg = u"ฅ/ᐠ。ᆽ。ᐟ\: [%s]" % result._host.get_name()
            color = C.COLOR_CHANGED
        else:
            if not self.display_ok_hosts:
                return

            if self._last_task_banner != result._task._uuid:
                self._print_task_banner(result._task)

            if delegated_vars:
                msg = u"ฅ(^・ω・^ฅ): [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
            else:
                msg = u"ฅ(^・ω・^ฅ): [%s]" % result._host.get_name()
            color = C.COLOR_OK

        self._handle_warnings(result._result)

        if result._task.loop and 'results' in result._result:
            self._process_items(result)
        else:
            self._clean_results(result._result, result._task.action)

            if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
                msg += " => %s" % (self._dump_results(result._result),)
            self._display.display(msg, color=color)

用nyaml插件制作猫舌

使用这个nyaml插件来执行ansible-playbook。

[zaki@manager nyan]$ ansible-playbook playbook.yml  
 [WARNING]: provided hosts list is empty, only localhost is available. Note
that the implicit localhost does not match 'all'


PLAY [localhost] ***************************************************************

TASK [Gathering Facts] *********************************************************
ฅ(^・ω・^ฅ): [localhost]

TASK [ping] ********************************************************************
ฅ(^・ω・^ฅ): [localhost]

TASK [shell] *******************************************************************
ฅ/ᐠ。ᆽ。ᐟ\: [localhost]

TASK [yum] *********************************************************************
Σ(;Φ ω Φ): [localhost]: FAILED! => changed=false 
  changes:
    installed:
    - ruby
  msg: |-
    You need to be root to perform this command.
  rc: 1
  results:
  - |-
    Loaded plugins: fastestmirror

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

[zaki@manager nyan]$

资料参考

    • ねこのAA

猫 | ねこ – 可愛い顔文字 | 顔文字まとめサイト
2chのかわいいAA/顔文字まとめ: 10/10更新 猫


顺便提一句,筆者從我的圖標你也可以看出來,我喜歡兔子?。

bannerAds