Ansible で コマンド実行結果の特定文字列有無で処理をスキップしたり、エラー（failed）させる　～　セキュリティパッチ当てのサンプルプレイブック（block:/rescue:/always:有り）で検証

# 0.はじめに

Ansibleで

 + あるコマンドの実行結果を元に処理を切り分けたい
 + ある特定の条件ではモジュールの処理は成功しても思った通りの状態になっていないこともあるのでチェックするコマンドを実行して、その結果によっては処理をfaildでエラーとしたい

という思いから、具体的には以下について CentOS6上でのopensslのセキュリティパッチ(アップデート)のyumのローカルインストール例として
block:/rescue:/always:の動作も合わせて動作検証を行いました。


1.コマンド実行結果に特定文字列が入っていなければ（逆も可能）処理する（入っている場合はskipする）
  + `when: "'評価したい文字列' not in command1_result.stdout"`
  + `when: command1_result.stdout.find('評価したい文字列') == -1`も同じですが、今回は `not in`を検証

2.コマンド実行結果に特定文字列が入っていなければ（逆も可能）処理をfailedさせる
  + `faild_when: "'評価したい文字列' not in command2_result.stdout"`
  + `faild_when: command2_result.stdout.find('評価したい文字列') == -1`も同じですが、今回は `not in`を検証
  
## 1.1. コマンド実行結果に特定文字列が入っていなければ処理する（入っている場合はskipする）
以下は変数（check_command）で定義、実行したコマンド実行結果の標準出力（command1_result.stdout）に変数（rpm_name）で指定した文字列が入っていなければ実行し、入っていれば処理をスキップします
※ `not in` の部分を`in`　とすれば逆の意味で特定文字列が入っている場合になります。

```
#  vars:
#    rpm_name: "openssl-1.0.1e-48.el6_8.1.x86_64"
#    check_command: "rpm -q openssl"
  - name: check rpm version
    command: "{{ check_command }}"
    register: command1_result
  - block:
# 中略 ～～～～～～～～～
    when: "'{{ rpm_name }}' not in command1_result.stdout"
#    when: command1_result.stdout.find('{{ rpm_name }}') == -1
```

## 1.2.コマンド実行結果に特定文字列が入っていなければ（逆も可能）処理をfailedさせる
以下は変数（check_command）で定義、実行したコマンド実行結果の標準出力（command2_result.stdout）に変数（rpm_name）で指定した文字列がなければfaildとする処理。今回はblock内での最終判定で利用。
※ `not in` の部分を`in`　とすれば逆の意味で特定文字列が入っている場合になります。

```
#  vars:
#    rpm_name: "openssl-1.0.1e-48.el6_8.1.x86_64"
#    check_command: "rpm -q openssl"
      - name: check rpm version
        command: "{{ check_command }}"
        register: command2_result
        failed_when: "'{{ rpm_name }}' not in command2_result.stdout"
#        failed_when: command2_result.stdout.find('{{ rpm_name }}') == -1
```

## 2.1. 実行環境
簡略化するため、Ansible 実行サーバ 及び Ansibleターゲットホストで実施します。

また、セキュリティパッチのopensslのrpmについては事前にpatchディレクトリ配下にダウンロードしておきます。

```
# cat /etc/redhat-release
CentOS release 6.7 (Final)
# ansible --version
ansible 2.1.0 (devel d358a22542) last updated 2016/04/04 23:43:05 (GMT +900)
  lib/ansible/modules/core: (detached HEAD a38bdfc720) last updated 2016/04/04 23:43:15 (GMT +900)
  lib/ansible/modules/extras: (detached HEAD 7c3999a92a) last updated 2016/04/04 23:43:22 (GMT +900)
  config file = /opt/qiita/patch/ex1/ansible.cfg
  configured module search path = Default w/o overrides
# tree patch/
patch/
- openssl-1.0.1e-16.el6_5.4.x86_64.rpm
- openssl-1.0.1e-42.el6_7.1.x86_64.rpm
- openssl-1.0.1e-48.el6_8.1.x86_64.rpm
```

## 2.2 サンプル playbook等

テスト用インベントリファイル
自分自身に対して実施

``` hosts
[local]
localhost ansible_connection=local
```

テスト用playbook

``` test.yml
---
- hosts: all
  become: yes
  become_user: root
# Don't gather hosts facts for performance
  gather_facts: no

# 'vars: to define in playbook Because the priority of 'variable is low,
# I use the following as a default value. When I overwrite, I define, for example, it in group vars.
  vars:
    rpm_name: "openssl-1.0.1e-48.el6_8.1.x86_64"
    rpm_src_dr: "patch"
    rpm_dest_dr: "/opt/work"
    rpm_src: "{{ rpm_src_dr }}/{{ rpm_name }}.rpm"
    rpm_dest: "{{ rpm_dest_dr }}/{{ rpm_name }}.rpm"
    check_command: "rpm -q openssl"

  tasks:
  - name: check rpm version
    command: "{{ check_command }}"
    register: command1_result

  - block:
      - debug: msg="Block START ----------------------"
      - name: create directory
        file: path={{ rpm_dest_dr  }} state=directory
      - name: copy the file
        copy: src={{ rpm_src }} dest={{ rpm_dest }} mode=0755
      - name: install rpm
        yum: name={{ rpm_dest }} state=present
      - name: check rpm version
        command: "{{ check_command }}"
        register: command2_result
        failed_when: "'{{ rpm_name }}' not in command2_result.stdout"
#        failed_when: command2_result.stdout.find('{{ rpm_name }}') == -1
      - debug: msg="Block END ------------------------"
    when: "'{{ rpm_name }}' not in command1_result.stdout"
#    when: command1_result.stdout.find('{{ rpm_name }}') == -1

    rescue:
      - debug: msg="install {{ rpm_name }} is error"

    always:
      - debug: msg="before {{ check_command }} {{ command1_result.stdout_lines }}"
        when: command1_result is defined
      - debug: msg="after  {{ check_command }} {{ command2_result.stdout_lines }}"
        when: command2_result is defined
```

---

## 3.1. 実行結果1
指定のバージョンのrpmがまだインストールされていない状況から、yumインストールが成功するパターン
以下は古いですが、[opensslのheart bleed](http://heartbleed.com/)の対策版にopensslのバージョンアップを行うサンプル

```
# ansible-playbook -i hosts test.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY ***************************************************************************

TASK [check rpm version] *******************************************************
changed: [localhost] => {"changed": true, "cmd": ["rpm", "-q", "openssl"], "delta": "0:00:00.067372", "end": "2016-06-23 14:33:57.949147", "rc": 0, "start": "2016-06-23 14:33:57.881775", "stderr": "", "stdout": "openssl-1.0.1e-16.el6_5.4.x86_64", "stdout_lines": ["openssl-1.0.1e-16.el6_5.4.x86_64"], "warnings": ["Consider using yum, dnf or zypper module rather than running rpm"]}
 [WARNING]: Consider using yum, dnf or zypper module rather than running rpm


TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "Block START ----------------------"
}

TASK [create directory] ********************************************************
changed: [localhost] => {"changed": true, "diff": {"after": {"path": "/opt/work", "state": "directory"}, "before": {"path": "/opt/work", "state": "absent"}}, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/opt/work", "secontext": "unconfined_u:object_r:usr_t:s0", "size": 4096, "state": "directory", "uid": 0}

TASK [copy the file] ***********************************************************
changed: [localhost] => {"changed": true, "checksum": "a7c21c4303f5efe855133eb40b633fc69d8673ba", "dest": "/opt/work/openssl-1.0.1e-48.el6_8.1.x86_64.rpm", "gid": 0, "group": "root", "md5sum": "a7a67c0c2c682b19a60d2cfe53e20521", "mode": "0755", "owner": "root", "secontext": "system_u:object_r:usr_t:s0", "size": 1597532, "src": "/root/.ansible/tmp/ansible-tmp-1466660038.73-68923506320964/source", "state": "file", "uid": 0}

TASK [install rpm] *************************************************************
changed: [localhost] => {"changed": true, "msg": "Warning: RPMDB altered outside of yum.\n", "rc": 0, "results": ["Loaded plugins: fastestmirror\nSetting up Install Process\nExamining /opt/work/openssl-1.0.1e-48.el6_8.1.x86_64.rpm: openssl-1.0.1e-48.el6_8.1.x86_64\nMarking /opt/work/openssl-1.0.1e-48.el6_8.1.x86_64.rpm as an update to openssl-1.0.1e-16.el6_5.4.x86_64\nDetermining fastest mirrors\n * base: ftp.jaist.ac.jp\n * epel: epel.scopesky.iq\n * extras: ftp.jaist.ac.jp\n * updates: ftp.jaist.ac.jp\nResolving Dependencies\n--> Running transaction check\n---> Package openssl.x86_64 0:1.0.1e-16.el6_5.4 will be updated\n---> Package openssl.x86_64 0:1.0.1e-48.el6_8.1 will be an update\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package  Arch    Version              Repository                          Size\n================================================================================\nUpdating:\n openssl  x86_64  1.0.1e-48.el6_8.1    /openssl-1.0.1e-48.el6_8.1.x86_64  4.0 M\n\nTransaction Summary\n================================================================================\nUpgrade       1 Package(s)\n\nTotal size: 4.0 M\nDownloading Packages:\nRunning rpm_check_debug\nRunning Transaction Test\nTransaction Test Succeeded\nRunning Transaction\n\r  Updating   : openssl-1.0.1e-48.el6_8.1.x86_64                             1/2 \n\r  Cleanup    : openssl-1.0.1e-16.el6_5.4.x86_64                             2/2 \n\r  Verifying  : openssl-1.0.1e-48.el6_8.1.x86_64                             1/2 \n\r  Verifying  : openssl-1.0.1e-16.el6_5.4.x86_64                             2/2 \n\nUpdated:\n  openssl.x86_64 0:1.0.1e-48.el6_8.1                                            \n\nComplete!\n"]}

TASK [check rpm version] *******************************************************
changed: [localhost] => {"changed": true, "cmd": ["rpm", "-q", "openssl"], "delta": "0:00:00.105701", "end": "2016-06-23 14:35:03.346040", "failed": false, "failed_when_result": false, "rc": 0, "start": "2016-06-23 14:35:03.240339", "stderr": "", "stdout": "openssl-1.0.1e-48.el6_8.1.x86_64", "stdout_lines": ["openssl-1.0.1e-48.el6_8.1.x86_64"], "warnings": ["Consider using yum, dnf or zypper module rather than running rpm"]}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "Block END ------------------------"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "before rpm -q openssl [u'openssl-1.0.1e-16.el6_5.4.x86_64']"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "after  rpm -q openssl [u'openssl-1.0.1e-48.el6_8.1.x86_64']"
}

PLAY RECAP *********************************************************************
localhost                  : ok=9    changed=5    unreachable=0    failed=0

#
```

## 3.2. 実行結果2
すでにheart bleed対策済みの該当のバージョンがインストールされているのでバージョンチェックのみを実施して後はblock:ごと処理をスキップするパターン

```
# ansible-playbook -i hosts test.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY ***************************************************************************

TASK [check rpm version] *******************************************************
changed: [localhost] => {"changed": true, "cmd": ["rpm", "-q", "openssl"], "delta": "0:00:00.103351", "end": "2016-06-23 14:45:07.905116", "rc": 0, "start": "2016-06-23 14:45:07.801765", "stderr": "", "stdout": "openssl-1.0.1e-48.el6_8.1.x86_64", "stdout_lines": ["openssl-1.0.1e-48.el6_8.1.x86_64"], "warnings": ["Consider using yum, dnf or zypper module rather than running rpm"]}
 [WARNING]: Consider using yum, dnf or zypper module rather than running rpm


TASK [debug] *******************************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [create directory] ********************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [copy the file] ***********************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [install rpm] *************************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [check rpm version] *******************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [debug] *******************************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [debug] *******************************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

TASK [debug] *******************************************************************
skipping: [localhost] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0

#
```

## 3.3. 実行結果3
既にインストールされているものよりも下位のバージョンをyumでインストールする。yumモジュールの実行は成功するが、おそらく依存の関係でバージョンダウンが失敗して、バージョンが実施前と変わらないパターン（rescue:とalways:が表示される）
バージョン指定については、--extra-varsを利用して、変数rpm_nameを上書きしている

```
# ansible-playbook -i hosts test.yml --extra-vars "rpm_name='openssl-1.0.1e-42.el6_7.1.x86_64'" -v
Using /etc/ansible/ansible.cfg as config file

PLAY ***************************************************************************

TASK [check rpm version] *******************************************************
changed: [localhost] => {"changed": true, "cmd": ["rpm", "-q", "openssl"], "delta": "0:00:00.100630", "end": "2016-06-23 14:48:41.170714", "rc": 0, "start": "2016-06-23 14:48:41.070084", "stderr": "", "stdout": "openssl-1.0.1e-48.el6_8.1.x86_64", "stdout_lines": ["openssl-1.0.1e-48.el6_8.1.x86_64"], "warnings": ["Consider using yum, dnf or zypper module rather than running rpm"]}
 [WARNING]: Consider using yum, dnf or zypper module rather than running rpm


TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "Block START ----------------------"
}

TASK [create directory] ********************************************************
ok: [localhost] => {"changed": false, "diff": {"after": {"path": "/opt/work"}, "before": {"path": "/opt/work"}}, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/opt/work", "secontext": "unconfined_u:object_r:usr_t:s0", "size": 4096, "state": "directory", "uid": 0}

TASK [copy the file] ***********************************************************
changed: [localhost] => {"changed": true, "checksum": "4dfba9eb2f646d1fd6fe4ad4ea3e4b38cf9a8ee8", "dest": "/opt/work/openssl-1.0.1e-42.el6_7.1.x86_64.rpm", "gid": 0, "group": "root", "md5sum": "eb23ba2042b7a7e551e09e16204a00d6", "mode": "0755", "owner": "root", "secontext": "system_u:object_r:usr_t:s0", "size": 1594240, "src": "/root/.ansible/tmp/ansible-tmp-1466660921.9-217212016697998/source", "state": "file", "uid": 0}

TASK [install rpm] *************************************************************
ok: [localhost] => {"changed": false, "msg": "", "rc": 0, "results": ["/opt/work/openssl-1.0.1e-42.el6_7.1.x86_64.rpm: Nothing to do"]}

TASK [check rpm version] *******************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["rpm", "-q", "openssl"], "delta": "0:00:00.087934", "end": "2016-06-23 14:48:43.558321", "failed": true, "failed_when_result": true, "rc": 0, "start": "2016-06-23 14:48:43.470387", "stderr": "", "stdout": "openssl-1.0.1e-48.el6_8.1.x86_64", "stdout_lines": ["openssl-1.0.1e-48.el6_8.1.x86_64"], "warnings": ["Consider using yum, dnf or zypper module rather than running rpm"]}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "install openssl-1.0.1e-42.el6_7.1.x86_64 is error"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "before rpm -q openssl [u'openssl-1.0.1e-48.el6_8.1.x86_64']"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "after  rpm -q openssl [u'openssl-1.0.1e-48.el6_8.1.x86_64']"
}

PLAY RECAP *********************************************************************
localhost                  : ok=8    changed=2    unreachable=0    failed=0

#
```