Splunkでカスタムサーチコマンドを作る（Streaming Command）

[Splunkで文字列を逆順にする。](https://qiita.com/toshikawa/items/807715dcb100c33b87ea)の最後あたりに

>頻繁に使うなら、外部pythonスクリプトを用意した方がいいかもしれません。

と書いたこともあり、作ってみました。　本当大変だった :sweat_smile: 

@uneyamauneko @msi さんの記事には本当助けられました。

コピペ元は[countmatches.py@splunk-sdk-python](https://github.com/splunk/splunk-sdk-python/blob/master/examples/searchcommands_app/package/bin/countmatches.py)

#準備
`pip install splunk-sdk` でいけると[Install the Splunk Enterprise SDK for Python](https://dev.splunk.com/enterprise/docs/devtools/python/sdk-python/gettingstartedpython/installsdkpython)には書いてある。

でも自分の環境

```zsh:環境変数等
% uname -v 
Darwin Kernel Version 19.6.0: Tue Nov 10 00:10:30 PST 2020
% which python
/opt/anaconda3/bin/python
% python --version
Python 3.7.6
% $SPLUNK_HOME/bin/splunk --version
Splunk 8.1.1 (build 08187535c166)
```

で`pip install splunk-sdk`すると

```bash:pip結果
 % pip install splunk-sdk
Requirement already satisfied: splunk-sdk in /opt/anaconda3/lib/python3.7/site-packages (1.6.14)
```

と入っているよと。確かに　`brew install splunk-sdk`した記憶あり。

ここで[例](https://dev.splunk.com/enterprise/docs/devtools/customsearchcommands/createcustomsearchcmd#Register-your-custom-search-command)の通り`commands.conf`を`$SPLUNK_HOME/etc/<<MYAPPS>>/default/`に設置
なお　https://docs.splunk.com/Documentation/Splunk/8.1.1/Admin/Commandsconf
の記載は間違っています。通報ずみ。

```configuration:commands.conf
[rev]
chunked = true
filename = rev.py
```

##重要事項
`/Applications/Splunk/lib/python3.7/site-packages`にlinkを作成する。

```
ln -s /Applications/Splunk/lib/python3.7/site-packages $SPLUNK_HOME/lib/python3.7/site-packages/splunklib 
```

```zsh:結果
% pwd
/Applications/Splunk/lib/python3.7/site-packages
% ls -la
lrwxr-xr-x    1 XXXXX  wheel     52 12 31 10:53 splunklib -> /opt/anaconda3/lib/python3.7/site-packages/splunklib
```

###理由
- 何度やっても `import splunklib`がうまくいかない
- `/usr/bin/env splunk` で起動した`python`だと`import splunklib`はうまく行っている。
- `$SPLUNK_HOME/bin/splunk cmd python`の`python`だと`import splunklib`は失敗する。
- 仕方がないので、シンボリックリンクを作成したらうまくいくようになった。

#Code

```python:rev.py
#!/usr/bin/env python

import sys
from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators

@Configuration()
class reverseCommand(StreamingCommand):
    """ Reverse the string.

    ##Syntax

    .. code-block::
        rev output=<field> <field>

    ##Description
    Outputs the string of the input field in reverse order.

    ##Example

    .. code-block::
        index=_internal clientip=* | head 1 | rev output=r_clientip clientip

    """

    output = Option(
        doc='''
        **Syntax:** **output=***<fieldname>*
        **Description:** Name of the field that will hold the revesed text''',
        require=True, validate=validators.Fieldname())

    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            for fieldname in self.fieldnames:
                pass
            record[self.output]=record[fieldname][-1::-1]
            yield record

dispatch(reverseCommand, sys.argv, sys.stdin, sys.stdout, __name__)

```

##使い方
`| rev output=<output field> <field>`

#結果

```javascript:rev.spl
index=_internal clientip=* | head 1
| rev clientip output=clientip_s
```

![rev.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130729/d6fc2871-9bcf-2339-fced-c2922bd93fd7.png)


##解説という名の苦労話
- ほとんど元ネタのコピペ
- [シバン](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%90%E3%83%B3_Unix)は効果がない（みたい）。

 最初に`commands.conf`と`rev.py`を所定の場所（`default`と`bin`）に格納してSplunkを再起動した。
 その時はこんなファイル。

```python:rev.py
#!/usr/bin/env python

import sys
from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators

@Configuration()
class reverseCommand(StreamingCommand):
    """ Reverse the string.
    """
    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            yield record

dispatch(reverseCommand, sys.argv, sys.stdin, sys.stdout, __name__)
```

何もしないコマンド。とりあえず、これで動作を確認した。
いったんこれで `|rev clientip`がエラーなく動作することを確認できた。

このあと、色々と変更しては、実行して結果をみて、修正しては実行を繰り返した。

このコマンドの主な処理は下記の通り。

```python:main
    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            for fieldname in self.fieldnames:
                pass
            record[self.output]=record[fieldname][-1::-1]
            yield record
```

`return`で戻り値を返さないで`yield`で返しているのがStreamingCommandなのかな？

いろいろ試していると、コマンドの引数であるフィールド名は`for fieldname in self.fieldnames:`で作った`fieldname`じゃないと取れないみたい。　
`unhashable type: 'list'`のエラーが出る。

<details><summary>リストを結合してみたら、無くても大丈夫だった。</summary><div>

```python:外してみた。
    def stream(self, records):
        self.logger.debug('revCommand: %s', self)  # logs command line
        for record in records:
            record[self.output]=record[''.join(self.fieldnames)][-1::-1]
            yield record
```
</div></details>


参考はこちら　[StreamingCommand.fieldnames](https://docs.splunk.com/DocumentationStatic/PythonSDK/1.6.13/searchcommands.html#splunklib.searchcommands.Configuration)

単純に上書きするだけなら
`record[fieldname]=record[fieldname][-1::-1]`でいいけど使い勝手が微妙だなとおもって、オプションを設定して上記に変更

#まとめ

初めて作ったこともあって、とても大変でした。
`import`ができないでかなりの時間が・・・

*ジョブの調査*をみてみるとちょっと時間がかかっているような気がします。
取り敢えずは、単純にリターンするだけのpythonだとこれをテンプレートに作れると思います。