StackStorm (st2) の workflow 作り方ポイントまとめ

# はじめに

この記事では [StackStorm (st2)](https://stackstorm.com/) で Workflow を作る際のポイントをまとめます。

ただしタイミングとして、StackStorm の難しいところを見つけたときに、その時点でのベスト・ベターな対応方法をポイントとしてまとめる形で、徐々に追記しながら充実させていこうと思っています。

## StackStorm とは

StackStorm は Workflow Engine です。

HTTP アクセス、SSH ログインしてリモートサーバでコマンド実行、Slack 通知、、、などの Action/Workflow をパズルのピースのように組み合わせて、柔軟にオペレーションの自動化を行うことが出来ます。

# StackStorm の難しいところ

Action や Workflow は YAML ファイルに定義を記述するのですが、ここに YAQL (Query Language) や jinja (template engine) が使うことが出来ます。

便利ではある一方で Action や Workflow を作っていく中で理解が難しく躓くこともしばしばあります。

# <font color="orange">[POINT]</font> YAML ファイルがパース・評価される順序

YAML が StackStorm により読み込まれ、Action/Workflow として実行される際に考慮すべきポイントです。

※ 想像です。ソースコードを読んで裏付けが出来たら追記します。もし詳しくご存じの方がいればコメントください。

1. YAML が解釈される
    * データ型は Python の対応するデータ型へ変換される
    * Action/Workflow が読み込まれるときに YAML による Syntax check が行われる
    * つまり、YAQL / jinja を使う場合も使わない場合も YAML として正しい記法に則る必要があります
1. YAML の文字列の中に書かれた YAQL / jinja 式が解釈される
    * 評価が終わった文字列は適切な Python データ型へ解釈される
        * String の "True" は boolean の True として解釈される
        * String の "[]" は空 Array として解釈される
        * ... etc
    * YAQL/jinja の実行結果と StackStorm の Action/Workflow 定義との Syntax check が行われる
    * YAQL/jinja は Action/Workflow が実行される際に評価される
    * つまり、実行時に Syntax error が発生する可能性があります

# <font color="orange">[POINT]</font> Action の default 値を設定するポイント

- YAML だけで定義する場合は、基本型(String、Array、Boolean、Object)が Workflow で定義する type と一致するようにする
- Key-Value(st2kv) が定義されていた場合のみ default 値を上書きできるようにする場合は `st2kv.system.XXX | trim | d(XXX, true)` の形式にする

## YAML だけで定義する場合

### String

```yaml
parameters:
  param1:
    type: string
    default: DEFAULT_STRING
```

### Array

```yaml
parameters:
  param1:
    type: array
    default:
      - item1
      - item2
```

### Boolean

```yaml
parameters:
  param1:
    type: boolean
    default: false
```

### Object

```yaml
parameters:
  param1:
    type: object
    default:
      key1: value1
      key2: value2
```

## Key-Value(st2kv) が定義されていた場合のみ default 値を上書きできるようにする場合

`st2kv.system.XXX` は st2kv に定義されているかどうかによらず、 `<st2common.services.keyvalues.KeyValueLookup object>` として評価されます。

そのため、定義された値であるかどうかは jinja でも評価できるように文字列へ変換する必要があります。
ここで、`trim` filter を使います。
すると、定義されていれば何らかの文字列となり、未定義の場合は空文字列("")となります。

続いて、default(省略形はd) filter へ渡すことで未定義の場合に default 値を設定することが出来ます。
ここで default filter の第 2 引数に false を設定した場合は undefined の値に対してのみ default 値を設定することになり空文字列が渡ってきても default 値を設定できません。
そこで第 2 引数に true を設定し、空文字列のような false として評価される値の場合に default 値を設定するようにしています。([参考](https://jinja.palletsprojects.com/en/2.11.x/templates/#default))

> This will output the value of my_variable if the variable was defined, otherwise 'my_variable is not defined'. If you want to use default with variables that evaluate to false you have to set the second parameter to true:

### String

```yaml
parameters:
  param1:
    type: string
    default: "{{ st2kv.system.some_value | trim | d('DEFAULT_STRING', true) }}"
```

- `st2kv.system.some_value` が設定されているとその文字列が default 値となる
- `st2kv.system.some_value` が設定されていいないと `"DEFAULT_STRING"` が default 値となる

```
+------------------+--------------+
| Property         | Value        |
+------------------+--------------+
| name             | some_value   |
| value            | HOGE         |
| scope            | st2kv.system |
| expire_timestamp |              |
+------------------+--------------+
```

### Array

```yaml
parameters:
  param1:
    type: array
    default: "{{ st2kv.system.some_value | trim | d([], true) }}"
```

- `st2kv.system.some_value` が設定されているとその値が default 値となる
- `st2kv.system.some_value` が設定されていいないと `[]` が default 値となる

```
+------------------+--------------+
| Property         | Value        |
+------------------+--------------+
| name             | some_value   |
| value            | [100,200]    |
| scope            | st2kv.system |
| expire_timestamp |              |
+------------------+--------------+
```

### Boolean

```yaml
parameters:
  param1:
    type: boolean
    default: "{{ st2kv.system.some_value | trim | d('True', true) }}"
```

- `st2kv.system.some_value` が設定されているとその値が default 値となる
- `st2kv.system.some_value` が設定されていいないと `"True"` が default 値となる

ここで、`st2kv.system.some_value` に "False" を設定した場合、default filter により "False" != false として判定されるため default filter は値を上書きしないという動作になります。

```
+------------------+--------------+
| Property         | Value        |
+------------------+--------------+
| name             | some_value   |
| value            | False        |
| scope            | st2kv.system |
| expire_timestamp |              |
+------------------+--------------+
```

### Object

```yaml
parameters:
  param1:
    type: object
    default: "{{ st2kv.system.some_value | trim | d('{\"k1\":\"v1\",\"k2\":\"v2\"}', true) }}"
```

```
+------------------+-----------------+
| Property         | Value           |
+------------------+-----------------+
| name             | some_value      |
| value            | {"key":"value"} |
| scope            | st2kv.system    |
| expire_timestamp |                 |
+------------------+-----------------+
```