Dockerで始めるStackstorm再入門2/3(条件分岐させるWorkflowと定期実行させるRuleを書く)

本記事は[Dockerで始めるStackstorm再入門1/3(環境構築からOrquestaで書いたWorkflowの結果をslackに通知する)](https://qiita.com/gkzz/items/4329f0846c873dbce9cf)の第2部です。

- [Dockerで始めるStackstorm再入門1/3(環境構築からOrquestaで書いたWorkflowの結果をslackに通知する)](https://qiita.com/gkzz/items/4329f0846c873dbce9cf)
- [Dockerで始めるStackstorm再入門2/3(条件分岐させるWorkflowと定期実行させるRuleを書く)](https://qiita.com/gkzz/items/85f119fc7771d5de582d)
- [Dockerで始めるStackstorm再入門3/3(pythonスクリプトとmockを使ったテストコードを書く)](https://qiita.com/gkzz/items/286f7b5ee3fd6674cb11)
- 番外編
  - [Dockerコンテナ上に立てたStacksormのWorkflowからAnsibleのPlaybookの結果をslackに通知する[junos_commandモジュール活用]](https://qiita.com/gkzz/items/ad52628e5f89d2ad6541)


## 0. 目次と構成図

本記事では第1部で取り上げることができなかったStackstormの構成ファイルの書き方についてお話しできればと思います。


- 1. 本記事でお話する構成図
- 2. Actionとは
- 3. Workflowとは
- 4. Workflowで引数や結果に応じて分岐させる書き方(基本編)
- 5. Workflowで引数や結果に応じて分岐させる書き方(shellscript編)
- 6. サンプルWorkflow
- 7. Ruleを使ってAction/Workflowを定期実行
- 8. shellscriptのデバッグについて
- 9. 次回予告

`6. サンプルWorkflow`では、リモートリポジトリのステータスの確認とコンテナの再立ち上げをshellscriptをActionの実行関数としながら、workflowを紹介し、その前の2から5でActionやworkflowの前提知識をお伝えしています。
最後にRuleとshellscriptのデバッグについて少し書いています。


## 1. 本記事でお話する構成図
第1部から抜粋します。
[Dockerで始めるStackstorm再入門1/3(環境構築からOrquestaで書いたWorkflowの結果をslackに通知する) 3. 構成図とバージョン情報](https://qiita.com/gkzz/items/4329f0846c873dbce9cf#3-%E6%A7%8B%E6%88%90%E5%9B%B3%E3%81%A8%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E6%83%85%E5%A0%B1)

![Screenshot from 2019-12-16 22-01-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/47b384ee-d9c0-3b27-6951-8237af7f3deb.png)
※1
Stackstormコンテナ内から同コンテナ外にあるアプリ(flask/nginx)コンテナに接続するために、
`ホストのDockerデーモンのソケットファイル(var/run/docker.sock)`をマウントさせています。

※2
Gtihubからリポジトリをcloneする際、ssh鍵やパスワードを使わずにおこなうべく、`Personal access tokens`を使いました。
トークンの取得方法は下記の記事をご参照ください。
私はrepoをスコープとしてトークンを取得しました。

参考
[Creating a personal access token for the command line - GitHub Help](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)


## 2. Actionとは

`Stackstormがおこなう処理`のことです。

### Actionの3種類(筆者調べ)

- core.localなどStackstormが提供するAction
- サードパーティが提供するAction(Ansible plabookがst2上で扱えるものなど)
- 独自に作ったAction


### Actionの構成ファイル

- 処理を定義するyamlファイル(メタファイル)
- Actionの実行スクリプト(Action Runnner

>Writing Custom Actions
>An action is composed of two parts:
>
>A YAML metadata file which describes the action, and its inputs.
>
>A script file which implements the action logic
>
>As noted above, an action script can be written in an >arbitrary programming language, as long as it follows these >conventions:
>
>Script should exit with 0 status code on success and non-zero >on error (e.g. 1)
>
>All log messages should be printed to standard error

参考: [Action Runners &mdash; StackStorm 3.1.0 documentation](https://docs.stackstorm.com/reference/runners.html)

### Actionの実行コマンド

```
root@$HOSTNAME:/# st2 run mydemo_pack.get_distro
```

![st2_run_action.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/dcb1b6ef-e628-7834-32c8-dcf0c3e2f4d4.png)
出所：近日参加予定のLT資料より。(完成次第、リンクを貼ります。)


### 処理を定義するActionのメタファイル
ここでは、私が作った`mydemo_pack`というPackのActionのメタファイルを一例として取り上げます。

### サンプルのActionでやっていること

- `cd $working_dir && git fetch -p　&& git checkout -q $branch && git status`をしてリモートリポジトリの更新を確認すること


```/opt/stackstorm/pack/mydemo_pack/actions/git_status.yaml
---
name: "git_status"
pack: "mydemo_pack"
description: "git status"
enabled: true
runner_type: "local-shell-script"　　# 後述します
entry_point: "scripts/git_status.sh"　# 本メタファイルからみた相対パスとなります。
parameters:
  working_dir:
    type: "string"
    required: true
    position: 0
  branch:
    type: "string"
    required: true
    position: 1
  expected:
    type: "string"
    required: true
    position: 2
```

### 公式ドキュメントで紹介されていたrunner_type
このように複数ありますが、だいたい使うのは文字の背景がグレーとなっているものかなと思います。

- `local-shell-cmd`
- `local-shell-script`
- `remote-shell-cmd`
- `remote-shell-script`
- `python-script`
- http-request
- action-chain
- `mistral-v2`
- cloudslang
- inquirer
- winrm-cmd
- winrm-ps-cmd 
- winrm-ps-script

参考: [Actions &mdash; StackStorm 3.1.0 documentation](https://docs.stackstorm.com/actions.html#action-runners)

なお、`orqeusta`については取り上げられていませんが、orqeustaを使う場合、workflowのメタファイルに`runner_type: "orquesta"`と書くことでorquestaを扱うことが出来ました。

参考:　
[st2/orquesta-streaming-demo.meta.yaml at master · StackStorm/st2](https://github.com/StackStorm/st2/blob/master/contrib/examples/actions/orquesta-streaming-demo.meta.yaml)


### Actionの実行スクリプト(Action Runnner)
これは先ほど`処理を定義するメタファイル`で取り上げたActionのメタファイルのなかの`entry_point: "scripts/git_status.sh"`です。

```/opt/stackstorm/packs/mydemo_pack/actions/scripts/git_status.sh

#!/bin/bash

# exit 0以外のリターンコードが返ることがあれば、そこで抜けるようにする
set -e

working_dir=$1
branch=$2
expected=$3

if [ -d "$working_dir" ]; then
  cd $working_dir
  sudo git fetch -p
  sudo git checkout -q $branch

  # ローカルリポジトリは最新であることを想定してgit status
  if [ "$expected" = "up_to_date" ]; then
    output=$(sudo git status | grep -E "(Your)\s+(branch)\s+(is)\s+(up-to-date)\s+(with)\s+('origin/$branch')" | awk '{print $6}' | grep -oP "$branch")
  # ローカルリポジトリは最新ではないこと(リモートリポジトリから更新を受け取る必要があること)を想定してgit status
  elif [ "$expected" = "not_up_to_date" ]; then
    output=$(sudo git status | grep -E "(Your)\s+(branch)\s+(is)\s+(behind)\s+('origin/$branch')" | awk '{print $5}' | grep -oP "$branch")
  else
    echo "None of the condition met"
  fi
  
  output=$(echo ${output:="unknown"})

  # git statusの結果が想定通りであるか
  if [ "$output" = "$branch" ]; then
    exit 0
  fi
fi
```


## 3. Workflowとは

- ひとつひとつの処理(Action)を下の図のように繋げたもの
![Screenshot from 2019-12-21 16-18-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/0bee6e41-e7c2-97c7-c69e-c2d657e7701b.png)

### workflowのメタファイル

- workflowのメタファイルの記載項目は、Actionのメタファイルのそれとほとんど変わらない
- 強いて言えばメタファイルを書くworkflowをst2 runで引数について定義することなく実行させる場合、`default: "hoge"`などと引数の値を定義する必要があるということと、`runner_type: "orquesta"`くらい
- workflowはActionと違い、メタファイルの拡張子とworkflow自身のファイルの拡張子が同じyamlと紛らわしい

※私はWorkflowのメタファイルは`*.meta.yaml`と命名しています。

```/opt/stackstorm/packs/mydemo_pack/actions/poll-repo.meta.yaml
---
name: "poll-repo"
pack: "mydemo_pack"
description: "poll repo"
runner_type: "orquesta"
entry_point: "workflows/poll-repo.yaml"
enabled: true
parameters:
  working_dir:
    type: "string"
    required: true
    default: "/usr/src/app/flask-docker"
  branch:
    type: "string"
    required: true
    default: "devel-views"
  expected:
    type: "string"
    required: true
    default: "up_to_date"
  ptn:
    type: "string"
    required: true
    default: "flask-docker_flask|flask-docker_nginx"
  timeout:
    type: "integer"
    required: true
    default: 300
```

## 4. Workflowで引数や結果に応じて分岐させる書き方(基本編)

以下の2つはAction問わず、使うと思います。

- Actionの成否
- 引数や変数の値に応じて


### Actionの成否

- `Actionが成功したというのは、Actionのreturn codeが0であるとき`、失敗したというのは、0以外であるとき(筆者調べ)

```Actionが成功した場合.yaml
- when: <% succeeded() %>` 
```

```Actionが失敗した場合.yaml
- when: <% failed() %>` 
```

### 引数や変数の値に応じて

```boolean値で分岐させる場合.yaml

tasks:
  init:
    action: core.noop
    next:
      - do: doSth
        publish:
          - failed: False  # boolean値を初期化
 doSth:
    action: xxxx
    next:
      - when: <% failed() %> 
        do: last
        publish:
          - failed: True　　# boolean値を更新

 last:
   action: core.noop
     next:
       - when: <% ctx().failed %>　# boolean値がTrueであるときfailを実行
         do: fail
　　　  #- when: <% not ctx().failed %>　# boolean値がFalseであるとき
       #  do: yyyy
 ```

```引数の値に応じて.抜粋.yaml

   # 「6. サンプルWorkflow」で取り上げているworkflowの抜粋です

　　# expectedはworkflowのメタファイルでdefault値として定義することと併せて、Actionの成否に応じて値を更新(publish)しています

    next:
      - when: <% succeeded() and (ctx().expected = 'up_to_date') %>
        do: last
      - when: <% succeeded() and (ctx().expected = 'not_up_to_date') %>
        do: git_merge
        publish:
          - failed: False
```

## 5. Workflowで引数や結果に応じて分岐させる書き方(shellscript編)

shellscriptでActionを実行する場合、Actionの結果に応じてworkflowを条件分岐する際に使うことができる方法は以下の3つです。(筆者調べ)

- Actionの成否(上述)
- 引数や変数の値に応じて(上述)
- リターンコードだけ

```リターンコードで分岐.抜粋.yaml

　# 「6. サンプルWorkflow」で取り上げているworkflowの抜粋です

  rebuild_app:　# 立ち上がっているコンテナを停止/削除した後、再立ち上げを図る
    action: mydemo_pack.rebuild_app
    input:
      working_dir: <% ctx().working_dir %>
      ptn: <% ctx().ptn %>
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() %>
        do: post_msg
        publish:
          - failed: False
          - action_result: <% result() %>
      - when: <% failed() %>
        do: check_failed
        publish:
          - action_result: <% result() %>
          - rc: <% result().return_code %>

  check_failed:　# rebuild_appがfailedした原因を調査
    action: core.noop
    next:
    - when: <% ctx().rc = 201 %>　 # failedの原因は停止/削除するコンテナがなかったことなので、改めてコンテナの再立ち上げを図る
      do: rebuild_app_cmd
    - when: <% ctx().rc != 201 %>
      do: post_msg
      publish:
      - failed: True 
```

リターンコードはshellscriptでこのように返却しています。

```/opt/stackstorm/packs/mydemo_pack/actions/scripts/rebuild_app.sh
#!/bin/bash

# exit 0以外のリターンコードが返ることがあれば、そこで抜けるようにする
set -e

working_dir=$1
ptn=$2
counter=0

if [ -d "$working_dir" ]; then
  cd $working_dir
  # image idを取得
  ids=$(sudo docker container ls | grep -E "${ptn}" | awk '{print $1}')
　# image idをfor-loopでstop/rmしていき、成功すれば$counterをインクルメント
　# image idがひとつもなければfor-loopは行われず、$counterもインクルメントされない
  for i in $ids;
  do
    sudo docker container stop $i \
    && sudo docker container rm $i;
    counter=`expr $counter + 1`
  done
  # image idから2回特定のコンテナをstop/rmsしている場合
  if [ $counter -eq 2 ]; then
    sudo docker-compose up -d --build
    counter=`expr $counter + 1`
  # 1度も特定のコンテナをstop/rmsしていない場合(そもそもコンテナが立ち上がっていない場合)
  elif [ $counter -eq 0 ]; then
    exit 201
  fi
fi

if [ $counter -eq 3 ]; then
  exit 0
fi
```


## 6. サンプルのWorkflow

ここまでお話したworkflowの書き方について盛り込んだサンプルのWorkflowの`/opt/stackstorm/packs/mydemo_pack/actions/workflows/poll-repo.yaml`とそれを図式化したので、ご参照ください。
![st2_run_wf_sh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/45afc3a5-1bab-6302-119d-c11e9ead113b.png)


- 左上の`polling/$SECONDS`が指している`git status`がサンプルのWorkflowの実質的に最初に行われるAction(実際は`failed=False`とフラグを初期化するcore.noopが最初に行うAction)

```/opt/stackstorm/packs/mydemo_pack/actions/workflows/poll-repo.yaml
version: 1.0

description: poll remote repo

input:
  - working_dir
  - branch
  - expected
  - ptn
  - timeout

output:
  - failed: <% ctx().failed %>
  - action_name: <% ctx().action_name %>

tasks:
  init:  # failedフラグを初期化(False)とするだけのAction
    action: core.noop
    next:
    - publish:
        - failed: False
        - action_name: 'poll_repo'
      do: git_status_before_merged
  
  git_tatus_before_merged: # ローカルリポジトリは最新であることを想定してgit status
    action: mydemo_pack.git_status
    input:
      working_dir: <% ctx().working_dir %>
      branch: <% ctx().branch %>
      expected: <% ctx().expected %> #デフォルト値は'up_to_date'
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() and (ctx().expected = 'up_to_date') %>
        do: last
      - when: <% succeeded() and (ctx().expected = 'not_up_to_date') %>　# 'not_up_to_date'(最新ではないこと)が確認できた
        do: git_merge
        publish:
          - failed: False
      - when: <% failed() and (not ctx().failed) %>
        do: git_status_before_merged
        publish:
          - failed: True
          - expected: 'not_up_to_date'
      - when: <% failed() and (ctx().failed) %>
        do: post_msg
        publish:
          - action_result: |-
              [result]
              <% result() %>
      
  git_merge:　　# git_status_before_mergedで、ローカルリポジトリは最新ではないことが確認出来きた場合のみ実行
    action: core.local
    input:
      cmd: sudo git merge origin/<% ctx().branch %>
      cwd: <% ctx().working_dir %>
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() %>
        do: git_status_after_merged  
        publish:
          - expected: 'up_to_date'
      - when: <% failed() %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-
              [result]
              <% result() %>
  
  git_status_after_merged:　　　　　# ローカルリポジトリは最新であるとしてgit_status_after_mergedを実行
    action: mydemo_pack.git_status
    input:
      working_dir: <% ctx().working_dir %>
      branch: <% ctx().branch %>
      expected: <% ctx().expected %>
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() %>
        do: rebuild_app
        publish:
          - action_result: |-
              [result]
              <% result() %>
      - when: <% failed() %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-
              [result]
              <% result() %>
  
  rebuild_app:　# 立ち上がっているコンテナを停止/削除した後、再立ち上げを図る
    action: mydemo_pack.rebuild_app
    input:
      working_dir: <% ctx().working_dir %>
      ptn: <% ctx().ptn %>
      timeout: <% ctx().timeout %>
    next:
      - when: <% succeeded() %>
        do: post_msg
        publish:
          - failed: False
          - action_result: <% result() %>
      - when: <% failed() %>
        do: check_failed
        publish:
          - action_result: <% result() %>
          - rc: <% result().return_code %>

  check_failed:　# rebuild_appがfailedした原因を調査
    action: core.noop
    next:
    - when: <% ctx().rc = 201 %>　 # failedの原因は停止/削除するコンテナがなかったことなので、改めてコンテナの再立ち上げを図る
      do: rebuild_app_cmd
    - when: <% ctx().rc != 201 %>
      do: post_msg
      publish:
      - failed: True 

  rebuild_app_cmd: # docker-compose up -d --buildを実行
    action: core.local
    input:
      cmd: sudo docker-compose up -d --build
      cwd: <% ctx().working_dir %>
      timeout: 600
    next:
      - when: <% succeeded() %>
        do: post_msg
        publish:
          - action_result: |-
              [result]
              <% result() %>
      - when: <% failed() %>
        do: post_msg
        publish:
          - action_result: |-
              [result]
              <% result() %>
  
  post_msg:　　# workflownの成否をslackに通知
    action: slack.post_message
    input:
      message: |-
        [action_name] 
        <% ctx().action_name %>
        [failed]
        <% ctx().failed %>
        [action_result]
        <% ctx().action_result %>
    next:
      - do: last
  
  last:　　# workflow全体の成否を決める上で考慮するべき失敗したActionがあるかfaildフラグで確認
    action: core.noop
    next:
      - when: <% ctx().failed %>
        do: fail　# workflow全体の結果をfailedとするorquestaが提供するEngine Command

```

### failとはなにか
- failは、`orquestaが提供する、workflow全体の結果をfailedとするorquestaが提供`するEngine Command
- そもそも`workflowのステータスは最後のActionのステータスによって決まるので、途中のActionの失敗をworkflowのステータスに反映出来ない`
- そこで使い方として、以下の使い方が考えられる
  - フラグを冒頭で定義して、
  - 任意のActionの成否に応じてフラグの値を更新し、
  - `最後のActionでフラグの値に応じてfailを実行`する


>The workflow engine will fail the workflow execution.

参考: [Orquesta Workflow Definition &mdash; StackStorm 3.1.0 documentation](https://docs.stackstorm.com/orquesta/languages/orquesta.html)


### シェルスクリプトでActionを走らせる場合の課題

- シェルスクリプトで走らせているActionの成否判定をWorkflowに適用させるには`return code`を使うしかない
- Stackstormでは`return codeが0のとき、そのActionのstatusは成功(succeeded)、0以外のときは失敗(fialed)`となっている
- そのため、シェルスクリプトを使ったActionでWorkflowを条件分岐させには、WorkflowのなかのActionのstatusがfailedとなることを許容するしかなく、結果としてWorkflow全体として成功したのか失敗したのか分かりづらくなってしまう


### ローカルリポジトリが最新であるときにworkflowが実行された場合
(Actionは全てSucceededとなり、見通しがよい)

```
root@$HOSTNAME:/# st2 run mydemo_pack.poll-repo
..
id: 5e0ac43ca38413013c720b10
action.ref: mydemo_pack.poll-repo
parameters: None
status: succeeded
start_timestamp: Tue, 31 Dec 2019 03:45:00 UTC
end_timestamp: Tue, 31 Dec 2019 03:45:03 UTC
result: 
  output:
    action_name: poll_repo
    failed: false
+--------------------------+------------------------+--------------------------+------------------------+-------------------------------+
| id                       | status                 | task                     | action                 | start_timestamp               |
+--------------------------+------------------------+--------------------------+------------------------+-------------------------------+
| 5e0ac43da38413003a9c56e8 | succeeded (0s elapsed) | init                     | core.noop              | Tue, 31 Dec 2019 03:45:01 UTC |
| 5e0ac43da38413003a9c56eb | succeeded (1s elapsed) | git_status_before_merged | mydemo_pack.git_status | Tue, 31 Dec 2019 03:45:01 UTC |
| 5e0ac43fa38413003a9c56ee | succeeded (0s elapsed) | last                     | core.noop              | Tue, 31 Dec 2019 03:45:03 UTC |
+--------------------------+------------------------+--------------------------+------------------------+-------------------------------+
root@$HOSTNAME:/# st2 execution get 5e0ac43da38413003a9c56eb
id: 5e0ac43da38413003a9c56eb
status: succeeded (1s elapsed)
parameters: 
  branch: devel-views
  expected: up_to_date
  timeout: 300
  working_dir: /usr/src/app/flask-docker
result: 
  failed: false
  return_code: 0
  stderr: ''
  stdout: ''
  succeeded: true
root@$HOSTNAME:/# 
```

### ローカルリポジトリが最新ではないときにworkflowが実行された場合
(ローカルリポジトリが最新であることを想定して実行されたAction(上から2個目のgit_status_before_merged)を除いてSucceededとなり、見通しは悪い。Workflow全体として成功しているのか、st2 execution get $IDで中身を精査すっる必要があるのか分かりづらい。)

```
root@$HOSTNAME:/# st2 run mydemo_pack.poll-repo
........
id: 5e0acdefa38413013c720b16
action.ref: mydemo_pack.poll-repo
parameters: None
status: succeeded
start_timestamp: Tue, 31 Dec 2019 04:26:23 UTC
end_timestamp: Tue, 31 Dec 2019 04:26:38 UTC
result: 
  output:
    action_name: poll_repo
    failed: false
+--------------------------+------------------------+--------------------------+-------------------------+-------------------------------+
| id                       | status                 | task                     | action                  | start_timestamp               |
+--------------------------+------------------------+--------------------------+-------------------------+-------------------------------+
| 5e0acdf0a38413003a9c5709 | succeeded (0s elapsed) | init                     | core.noop               | Tue, 31 Dec 2019 04:26:24 UTC |
| 5e0acdf0a38413003a9c570c | failed (2s elapsed)    | git_status_before_merged | mydemo_pack.git_status  | Tue, 31 Dec 2019 04:26:24 UTC |
| 5e0acdf2a38413003a9c570f | succeeded (1s elapsed) | git_status_before_merged | mydemo_pack.git_status  | Tue, 31 Dec 2019 04:26:26 UTC |
| 5e0acdf4a38413003a9c5712 | succeeded (0s elapsed) | git_merge                | core.local              | Tue, 31 Dec 2019 04:26:28 UTC |
| 5e0acdf4a38413003a9c5715 | succeeded (1s elapsed) | git_status_after_merged  | mydemo_pack.git_status  | Tue, 31 Dec 2019 04:26:28 UTC |
| 5e0acdf6a38413003a9c5718 | succeeded (5s elapsed) | rebuild_app              | mydemo_pack.rebuild_app | Tue, 31 Dec 2019 04:26:30 UTC |
| 5e0acdfca38413003a9c571b | succeeded (1s elapsed) | post_msg                 | slack.post_message      | Tue, 31 Dec 2019 04:26:36 UTC |
| 5e0acdfda38413003a9c571e | succeeded (1s elapsed) | last                     | core.noop               | Tue, 31 Dec 2019 04:26:37 UTC |
+--------------------------+------------------------+--------------------------+-------------------------+-------------------------------+
root@$HOSTNAME:/# st2 execution get 5e0acdf0a38413003a9c570c
id: 5e0acdf0a38413003a9c570c
status: failed (2s elapsed)
parameters: 
  branch: devel-views
  expected: up_to_date
  timeout: 300
  working_dir: /usr/src/app/flask-docker
result: 
  failed: true
  return_code: 1
  stderr: "From https://github.com/gkzz/flask-docker
   83ce3b6..027a009  devel-views -> origin/devel-views"
  stdout: ''
  succeeded: false
root@$HOSTNAME:/# st2 execution get 5e0acdf2a38413003a9c570f
id: 5e0acdf2a38413003a9c570f
status: succeeded (1s elapsed)
parameters: 
  branch: devel-views
  expected: not_up_to_date
  timeout: 300
  working_dir: /usr/src/app/flask-docker
result: 
  failed: false
  return_code: 0
  stderr: ''
  stdout: ''
  succeeded: true
root@$HOSTNAME:/# 
```


## 7. Ruleを使ってAction/Workflowを定期実行
以下のyamlを作ってください。

```
root@$HOSTNAME:/# cat /opt/stackstorm/packs/mydemo_pack/rules/timer.yaml
---
name: "timer"
pack: "mydemo_pack"
description: "run mydemo per 300 secs"
enabled: true
trigger:
  type: "core.st2.IntervalTimer"
  parameters:
    unit: seconds
    #delta: 30
    delta: 300
action:
  ref: "mydemo_pack.poll-repo"
  parameters:
    working_dir: "/usr/src/app/flask-docker"
    branch: "devel-views"
    expected: "up_to_date"
    ptn: "flask-docker_flask|flask-docker_nginx"
```

それではRuleを作るコマンドを以下のとおり実行すれば設定完了です。

```
root@$HOSTNAME:/# st2 rule create timer.yaml
```

## 8. shellscriptのデバッグについて

以下の2つの方法でデバッグさせていました。

- Action単体で実行

```
root@$HOSTNAME:/# st2 run mydmeo_pack.git_status
```

- bash -x /path/to/$filenameでデバッグ

```
root@$HOSTNAME:/# bash -x /opt/stackstorm/packs/mydemo_pack/actions/scripts/git_status.sh \
> /usr/src/app/flask-docker \
> flask-docker_flask|flask-docker_nginx
```

## 9. 次回予告

shellscriptでリモートリポジトリのステータスの確認とコンテナの再立ち上げを行いましたが、今度はそれをpythonでおこなってみます。
pythonでActionの実行スクリプトを書くと、`shellscriptに比べてActionの返却値(return value)を柔軟に定義`することができたり、`mockを使ってテストコードも書く`ことができるので、よりきめ細かくActionを書くことができます。

お楽しみに！！

鋭意作成中
Dockerで始めるStackstorm再入門3/3(pythonスクリプトとmockを使ったテストコードを書く)


## 参考

- [Actions StackStorm 3.1.0 documentation](https://docs.stackstorm.com/actions.html)

- [Ubuntu Linux を実行しているプライベート Amazon EC2 インスタンスに静的ホスト名を割り当てる]
(https://aws.amazon.com/jp/premiumsupport/knowledge-center/linux-static-hostname)

- [シェルスクリプトのreturn - 考える人、コードを書く人]
(https://bokko.hatenablog.com/entry/20090223/1235400626)

- orqeustaのサンプルコード

  - [github.com/StackStorm/st2/blob/master/contrib/examples/actions/workflows/orquesta-streaming-demo.yaml]
(https://github.com/StackStorm/st2/blob/master/contrib/examples/actions/workflows/orquesta-streaming-demo.yaml)

  - [github.com/StackStorm/st2/blob/master/contrib/examples/actions/local_command_runner_print_to_stdout_and_stderr.yaml](https://github.com/StackStorm/st2/blob/master/contrib/examples/actions/local_command_runner_print_to_stdout_and_stderr.yaml)


- 本記事で扱ったStackstormのサンプルコード
  - [gkzz/st2-docker-gkz](https://github.com/gkzz/st2-docker-gkz)

- Stackstormの管理対象のアプリコンテナ
  - [gkzz/flask-docker](https://github.com/gkzz/flask-docker)

- ActionとWorkflowのサンプルコード
  - [gkzz/mydemo_pack](https://github.com/gkzz/mydemo_pack)


## P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます:)
[@gkzvoice](https://twitter.com/gkzvoice)

## P.P.S. StackstormでLTしました！

[StackstormというIFTTT的なツールをDockerコンテナに載せた小話](https://speakerdeck.com/gkzz/stackstormtoiuiftttde-naturuwodockerkontenanizai-setaxiao-hua)