StackStormでPythonのカスタムスクリプトを利用する

StackStormのActionには、カスタムスクリプトとしてPythonが利用できます。

* [イベントドリブンな自動化ツール StackStormを試す
](http://qiita.com/unchemist/items/0e6425396ba1a1e6b43c)

ここでは、StackStormでPythonスクリプトを利用するための流れと使い方を見てみます。

## pythonスクリプトの構成要素

以下の2つです。

* metadata : YAML形式で、基本的な設定などを行います。
    * Action名
    * pythonスクリプトに与えるパラメータ(その型や必須かどうかも含めて)
    * 呼び出すpythonスクリプト
* pythonスクリプト : 普通のpythonスクリプトです。

静的なconfigファイルを利用するなら、さらにもう1つconfigファイルを利用します。

## 動かす流れ

* スクリプトや設定を作る
* (必要に応じて)StackStorm上での実行環境を作る
* Actionを登録する
* 動かす

という流れになります。

## 実際にActionとしてPythonスクリプトを動かす

ここからは実際にpythonスクリプトをActionとして動かす流れを見ていきましょう。
事前に認証を済ませているものとします。
以下は`st2admin` / `st2admin` で認証している例です。

```bash
$ export ST2_AUTH_TOKEN=$(st2 auth st2admin -p st2admin -t)
```

### 必要なスクリプトや設定ファイルを作る

上記の通り、2つのファイルを作る必要があります。
どちらも、 `/opt/stackstorm/packs/<pack名>/actions/` 配下に置かなければなりません。
ここでは`test` というpack名で作成します。

#### metadata

こちらは、割とすんなりとみることができるのではないでしょうか。
`entry_point` は、 `/opt/stackstorm/packs/<pack名>/actions`からみた相対パスを書きます。

```yaml:/opt/stackstorm/packs/test/actions/python-test.yaml
---
name: "python-test"
runner_type: "python-script"
description: "this is a sample script of python"
enabled: true
entry_point: "python-test.py"
parameters:
  param1:
    type: "string"
    description: "parameter 1"
    required: true
    position: 0
  param2:
    type: "string"
    description: "parameter 2"
    required: true
    position: 1
    default: "default parameter 2"
```

#### pythonスクリプト

こちらのスクリプトは、StackStormから呼び出されるためお作法自体はありますが、結局は `run` メソッドが呼ばれると思えば良さそうです[^1]。

[^1]: https://docs.stackstorm.com/actions.html#writing-custom-python-actions

```python:/opt/stackstorm/packs/test/actions/python-test.py
import sys

from st2actions.runners.pythonrunner import Action

class PythonTestAction(Action):
    def __init__(self, config):
        self.config = config

    def run(self, param1, param2):
        print(param1, param2)
        print(self.config["test_param1"])
        return (True, param1 + "," + param2)
```

コンストラクタで`config`を渡していますが、これが次に紹介するconfigファイルと連動します。

#### configファイル

```yaml:/opt/stackstorm/packs/test/config.yaml
test_param1: "static_param1"
```

`<pack名>`の直下に`config.yaml` を置いておくと、そこの中に書かれた設定値をpythonスクリプト実行時にコンストラクタで`config`というオブジェクトに読み込んでくれるそうです[^2]。

[^2]: https://docs.stackstorm.com/actions.html#configuration-file

### StackStorm上でpythonの実行環境を作る

さて、これでStackStormに登録して実行…といきたいところですが、このまま登録して実行しても、エラーになります。
(登録は成功しますが実行はエラーになります)

まず、全てのActionを登録するには、 `st2ctl reload --register-actions` コマンドを実行します。
これは、 `/opt/stackstorm/<pack名>` 配下にある設定を全て読みなおしてくれるというコマンドになります。

```bash
$ sudo st2ctl reload --register-actions
Registering content...[flags = --register-actions]
2016-08-27 14:23:15,107 INFO [-] Connecting to database "st2" @ "0.0.0.0:27017" as user "None".
2016-08-27 14:23:15,376 INFO [-] =========================================================
2016-08-27 14:23:15,376 INFO [-] ############## Registering actions ######################
2016-08-27 14:23:15,376 INFO [-] =========================================================
2016-08-27 14:23:17,147 INFO [-] Registered 63 actions.
##### st2 components status #####
st2actionrunner PID: 4291
st2actionrunner PID: 4293
st2actionrunner PID: 4295
st2actionrunner PID: 4297
st2actionrunner PID: 4299
st2actionrunner PID: 4301
st2actionrunner PID: 4303
st2actionrunner PID: 4305
st2actionrunner PID: 4307
st2actionrunner PID: 4309
st2api PID: 4316
st2api PID: 4810
st2stream PID: 4378
st2stream PID: 4756
st2auth PID: 4388
st2auth PID: 4748
st2garbagecollector PID: 4396
st2notifier PID: 4404
st2resultstracker PID: 4416
st2rulesengine PID: 4428
st2sensorcontainer PID: 4440
st2chatops is not running.
mistral-server PID: 4486
mistral-api PID: 4481
mistral-api PID: 4769
mistral-api PID: 4770
```

実際に登録されています。

```
$ st2 action list -p test
+----------------------+------+-----------------------------------+
| ref                  | pack | description                       |
+----------------------+------+-----------------------------------+
| test.python-test     | test | this is a sample script of python |
+----------------------+------+-----------------------------------+
```

さて、これで実行…とすると、エラーになります。

```bash
$ st2 run test.python_test param1=p1
..
id: 57c1dbf1e368b912caaf8908
status: failed
parameters:
  param1: p1
result:
  error: '

    Virtual environment (/opt/stackstorm/virtualenvs/test) for pack "test" doesn''t exist. If you haven''t

    installed a pack using "packs.install" command, you can create a new virtual environment using

    "st2 run packs.setup_virtualenv packs=test" command''

    '
  traceback: "  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2actions/container/base.py", line 98, in _do_run
    (status, result, context) = runner.run(action_params)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2actions/runners/pythonrunner.py", line 118, in run
    raise Exception(msg)
"
```

途中にメッセージもありますが、 `test`というpackでのpythonの実行環境がないということですね。`virtualenv`によって、packごとに実行環境がわけられるようです。
ですので、packを適切に分割すれば、実行環境がごちゃごちゃになるのを避けることができそうです。

さて、メッセージ通りに環境を作りましょう。丁寧に「コマンドをこう叩け」と書いてあるので、そのとおりに実行します。

```bash
$ sudo st2 run packs.setup_virtualenv packs=test
....
id: 57c1dc6ce368b912caaf890b
status: succeeded
parameters:
  packs:
  - test
result:
  exit_code: 0
  result: 'Successfuly set up virtualenv for the following packs: test'
  stderr: 'st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Setting up virtualenv for pack "test"

    st2.actions.python.SetupVirtualEnvironmentAction: INFO     Virtualenv path "/opt/stackstorm/virtualenvs/test" doesn''t exist

    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Creating virtualenv for pack "test" in "/opt/stackstorm/virtualenvs/test"

    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Creating virtualenv in "/opt/stackstorm/virtualenvs/test" using Python binary "/opt/stackstorm/st2/bin/python"

    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Running command "/opt/stackstorm/st2/bin/virtualenv -p /opt/stackstorm/st2/bin/python /opt/stackstorm/virtualenvs/test" to create virtualenv.

    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Installing base requirements

    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    No pack specific requirements found

    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Virtualenv for pack "test" successfully created in "/opt/stackstorm/virtualenvs/test"

    '
  stdout: ''
```

全てINFOとDEBUGなので問題なさそうです。最後のメッセージの通り、`/opt/stackstorm/virtualenvs/test` にpythonの環境ができています。

### Actionの実行(Pythonスクリプトの実行)

さて、登録自体はできているので再度実行してみましょう。

```bash
$ st2 run test.python-test param1=p1
..
id: 57c1ed13e368b912caaf8962
status: succeeded
parameters:
  param1: p1
result:
  exit_code: 0
  result: p1,default parameter 2
  stderr: ''
  stdout: '(u''p1'', u''default parameter 2'')

    static_param1

    '
```

上記の通り、`print` で出力されたものは`stdout`に、`return`で戻すものは`result`に格納されています。

## まとめ

ということで、簡単ですがpythonスクリプトを実行する流れを見てきました。
構成要素は少し多いかもしれませんが、非常に簡単にpythonをworkflowとして動かすことができそうです。

## その他気になったこと

### Action実行中に、そのActionを変更してreloadしたらどうなるの？

どうにもなりませんでした。中断もされないし動かされたものは動き続けます。
変更されたものは、次回実行時から反映されます。
従って、わざわざ既存で動いているものを止めずに済みそうです。

### reloadが必要なときはどんなとき？

YAML(metadata)修正時です。
pythonスクリプトを修正するだけならreloadは不要です。

### ファイル消してreloadしたらどうなる？

どうにもなりませんでした。
今までないものを`st2ctl reload --register-actions`コマンドにより反映はできますが、
逆にファイルを消して上記コマンドを実行しても消えません。(Actionは登録されたままになります)
従って、消す場合は以下のようにして明示的に消すしかなさそうです。

```
st2 action delete test.python-test
```

### 引数以外にどんなパラメータが使えるの？

Pythonスクリプトに関して言うなら、基本的に明示的に引数に加えるか、環境変数から読み取るかくらいしかできなさそうです[^3]。

[^3]: https://docs.stackstorm.com/actions.html#built-in-parameters

### 引数にはどんなものが使えるの？

`{{ action_context }}` という形で、その辞書の配下にいろいろと入っているらしいです[^4]。
例えばWebHookだったらそのユーザとか、Workflowだったら親ActionのIDとか。
ただ、試していません。

[^4]: https://docs.stackstorm.com/actions.html#parameters-in-actions