2019年2月19日火曜日

[Azure Pipelines] ファイルがなかった場合にステップをスキップする

Add an exists() on Task Custom Condition - Developer Community



とあるファイルがある場合にだけ実行する Job/Step が欲しくなったのですが、condition に簡単に書けたら嬉しかったのですが、今の所ない模様。

こんな感じできたら・・・
- script: |
    echo "xml がある場合にしたいこと"
  condition: exist('./test/*.xml')


状態を variable に記録して条件とする
ワンステップではできないものの、比較的簡単にやりたいことは実現できます。

condition で variables を参照してスキップさせることはできるので、ファイルが存在しない場合にスキップ用の variable をセットするようにしました。

- script: |
    TEST_RESULTS=`find ./test -maxdepth 1 -name *.xml 2>/dev/null`
    if [ $? -ne 0 ] || [ -z "$TEST_RESULTS" ]; then echo "##vso[task.setvariable variable=XmlFileNotExist]true"; fi

- script: |
    echo "xml がある場合にしたいこと"
  condition: ne(variables['XmlFileNotExist'], 'true')



この方法であれば、「ファイルが存在するかどうか」以外でも、条件付けできそうですね。

2019年2月12日火曜日

Windows + Git で実行権限をつけて commit する

何回かハマって、何回も調べ直してるので、記憶の定着・備忘録のために自分のブログにも書き残しておくことにした。

Windows + Git Bash では 644 で commit される
chmod +x としておいても commit したときに書き換えられます。
$ git commit -m 'sample action a'
[master e432bd4] sample action a
 4 files changed, 21 insertions(+), 4 deletions(-)
 create mode 100644 action-a/Dockerfile
 create mode 100644 action-a/README.md
 create mode 100644 action-a/entrypoint.sh

この状態でもう一回 chmod +x しても差分として出てこない。

git update-index --add --chmod=+x
パーミッションの変更は上記コマンドで行います。

しょっちゅう忘れるので alias にしました。
git config --global alias.addx 'update-index --add --chmod=+x'

これで、addx エイリアスから chmod +x できるようになりました。



+x/-x 両方に対応したエイリアス
せっかくなのでもう少し汎用的なエイリアスも用意しました。
git config --global alias.chmod '!f(){ cd ${GIT_PREFIX:-.} && git update-index --add --chmod=$1 ${@:2:($#-1)};};f'

git chmod +x hoge
git chmod -x foo bar
のように使えます。

2019年2月4日月曜日

.editorconfig ファイル自体の Lint を CI で回す



[*.{cpp,hpp,ipp}}
charset = utf-8-bom
単純な typo ですが、今までずーと気づかずに config が仕事してくれてると思い込んでました。。。
直すのは簡単です。簡単ですが、また同じ失敗をする可能性があります。(にんげんだもの)

というわけで、テスト書きました。

テスト方法
.editorconfig の記法は概ね ini ファイルと一致します。
なので、適当な ini ファイルパーサーに読み込ませてエラーが起きないかどうか?で検証できそうです。

iutest は C++ のテスティングフレームワークですが、ツール系には Python も使用しています。
Python には ConfigParser があるのでそちらでテストを書きました。

import sys
import os

try:
    from configparser import ConfigParser
except ImportError:
    from ConfigParser import SafeConfigParser as ConfigParser


class EditorConfig(object):
    def __init__(self, path):
        self.name = os.path.basename(path)
        self.fp = open(path)
        self.first_head = True

    def readline(self):
        if self.first_head:
            self.first_head = False
            return '[global]\n'
        return self.fp.readline()

    def __iter__(self):
        return self

    def __next__(self):
        line = self.readline()
        if not line:
            raise StopIteration
        return line

    next = __next__  # For Python 2 compatibility.


def main():
    path = sys.argv[1]
    ini = ConfigParser()
    if not os.path.exists(path):
        sys.stderr.write('%s not found...' % path)
        sys.exit(2)
    ini.readfp(EditorConfig(path))
    sys.exit(0)

if __name__ == '__main__':
    main()

基本的には ini ファイルをパーサーにかけるだけなんですが、1点だけポイントがあります。
EditorConfig は、ディレクトリを上がって .editorconfig を探索しますが、root = true を最初に書いておくとそこで探索が打ち切られます。
この root = true が ini ファイルとしてはセクションのない要素のためエラーになってしまいます。
そのため、最初に [global] というダミーセクションを挿入して ConfigParser に渡してます

CI に組み込む
Python が実行できる CI サービスであればなんでも問題ありません。
今回 iutest では Codeship で cpplint やインクルードガードのチェックや絶対パスが混入してないかの、雑多なテストを行っているのでそちらに組み込みました。



こんな感じに検出されます!
これでもう安心ですね。

2019年1月29日火曜日

Integromat で役目を終えたシナリオを自動的に停止させる

Integromat はシナリオでエラーが起こると、実行をしばらくポーズします。
ポーズしている間に、エラーを直しましょう。ということだと思いますが、
エラーのままにしておいて、一定回数ポーズをするとシナリオは Disable にされます。


(だんだんとポーズ時間が延びていき、6回目でシナリオが無効になりました。)

今回はこの挙動を利用して、役目を果たしたシナリオが自動的に Disable になるようにしてみました。

やり方
まず、役目を果たしたことをシナリオが認知しなければいけません。
その点はシナリオによって変わってくるので、ここでは説明しません。
今回は、すでに役目を果たしたことをシナリオで検出できており、そのノードが出来上がっている前提とします。

以下が、そのシナリオです。
Slack 通知が2つあると思いますが上が正常ノード。下は期間終了後のノードで、赤い丸をつけたところが、今回のポイントです。


この HTTP GET オペレーションは、存在しない適当なアドレスに GET リクエストをします。
このオペレーションは失敗します。が、これで OK です。


簡単にいうと、わざと失敗させています。
失敗すればなんでもいいので、正常ケースのオペレーションが、役目を果たしたあとの場合に異常ケースで失敗してしまう、でも(今回の目的では)問題ないです。

なぜこんなことが必要なのか


Free プランで使っているのですが、ちょっとリッチなシナリオを書いたり、一日に何回もトリガーする cron 設定にしたりすると、すぐに 1,000 operations 消費してしまってシナリオ実行ができなくなってしまって困ってました。
Operation の数を削減することもできるのですが、30分毎にトリガーすると 2 x 24 x 30 = 1440 で、仮に 1 operation でも全然足りません。

ただ、今回の場合、30分毎にトリガーする必要はあるものの、1ヶ月間ずっと必要ではなく1週間程度で良かったので、必要なときだけシナリオを有効にすれば OK でした。







自動化したいじゃないですが!

というわけで今回は、「自動で無効にする」ができました。
次は、「自動で有効にする」ができたら報告したいと思います。
(有効化は設定を書き換える必要があったりするので少し手間取りそうですが・・・)

では。

2019年1月22日火曜日

[GitLab CI] マトリックスを組んでみた

GitLab CI でマトリックスを組んでみました。

Matrix Builds in CI - Questions & Answers / GitLab CI - GitLab Forum
方法は2つあって、YAML のアンカーを使った方法と、それと同等の extends を使った方法です。
(マトリックス分べた書きすればどんな CI サービスでも複数ジョブは実現できるが、重複する冗長な部分をまとめて簡潔に書けますよーという感じ)

Anchor を使った方法
YAML にはアンカーとエイリアス記法が存在します。
これは GitLab の YAML の機能ではなく、YAML の標準的な記法のため、別の CI サービスでも応用できると思います

Anchors - Configuration of your jobs with .gitlab-ci.yml | GitLab
以下は公式の例を参照しています。
.job_template: &job_definition  # Hidden key that defines an anchor named 'job_definition'
  image: ruby:2.1
  services:
    - postgres
    - redis

test1:
  <<: *job_definition           # Merge the contents of the 'job_definition' alias
  script:
    - test1 project

test2:
  << *job_definition           # Merge the contents of the 'job_definition' alias
  script:
    - test2 project

&名前 でアンカーを作成し、*名前でアンカーを参照します。
また、 「<< *名前」とすると、アンカーの内容をマージしてくれます。

つまり、共有部分をアンカーとして定義し、マトリックスジョブに可変部分を定義、あとは共有部分をマージすれば簡単にマトリックスを組むことができます。
(今回は後述の extends で書いたので実例は省略)

extends を使った方法
こちらは GitLab 11.3 で導入された GitLab の機能になります。
アンカーを書かずとも、extends で指定した YAML 要素を展開できる機能のようです。
アンカー記法やその参照方法を知らなくても、より直感的に書けるのでこちらをオススメします

extends - Configuration of your jobs with .gitlab-ci.yml | GitLab
アンカーの例を extends で書き直すと以下のようになります。」
.job_template:
  image: ruby:2.1
  services:
    - postgres
    - redis

test1:
  extends: .job_template
  script:
    - test1 project

test2:
  extends: .job_template
  script:
    - test2 project
(アンカー記法を見たあとだと、どっちでも良くない?となりますが、知らない人からしたら読みやすくなってるとは思います。)

iutest-test での実例
さて、最後に実例として iutest-test の例を紹介したいと思います。
iutest-test がどういったもので、どんな CI をしているかは以前に書いたこちらの記事を参考にしてください。
ブログズミ: iutest のテストをするリポジトリ iutest-test を GitLab/GitLab CI に引っ越しました

今回、マトリックスを組むことにした理由は、CI するブランチの対象を master のみから、master|develop に変更しようと思ったからです。
iutest-test でしているテストの重要度は高いわけではないので、1日1回 master ブランチを対象としていましたが、やっぱり master にマージしてからじゃないと状態がわからないのはよくないなと思い、develop でも1日1回実行すようにしました。


YAML はこちら→iutest-test / .gitlab-ci.yml


これで、定期ビルドでサブモジュールの更新が master / develop 2つに対して行われるようになりました。

最後に
今回のような定期ビルドの環境変数をマトリックスにしたいようなケースはパイプラインでなくても、トリガー固有の環境変数定義でもできると思います。
ただ、YAML のアンカーにしろ、extends にしろ、書き方を知っておくと、いざというときに役に立つのではないかと思います。

というわけで、早速アンカーを使った他の CI サービスの YAML を DRY にしようと思います。
今回は以上です。
では。

2019年1月15日火曜日

[Buddy] not enough disk space でエラーが出たのでキャッシュ削除をした話


Build failed: not enough disk space. The size of files generated by your build exceeds 5120 MB. Please run the execution with 'Clear cache' to free space in the filesystem or contact support@buddy.works to increase the size limit.
Action failed: see logs above for details

キャッシュクリアしたら回復しそうなので、方法を調べました。

マニュアル実行のオプション
調べるまでもなく、マニュアル実行するときのオプションがあるのを(たぶん) Buddy を使ってる人なら知っているでしょう。


しかし、これをやっても面白みがありませんし、できるならば手動実行ではなく、自動で勝手にいい感じにキャッシュクリアできたら一番です。
というわけで、検索してみました。
(Not Recommended ですし…)

コミットメッセージコマンドからクリアする
Buddy v1.5.1 Released - Buddy

だいぶ前のリリースノートですが、コミットメッセージに「--clear-cache」と入れると、キャッシュをクリアしてくれるようです。

How Use Commit Commands - Buddy
ドキュメントには一切書かれてませんが、試してみました。




結果は・・・


キャッシュクリアされてました!

でも、ビルドは失敗してる・・・

デフォルトでキャッシュクリアするように YAML に書く
iutest のパイプラインは as Code しているので、YAML で設定できないか調べてみました。
Yaml Schema - Buddy


auto_clear_cache Boolean Defines whether or not to automatically clear cache before running the pipeline .
YAML Schema を見てみると、auto_clear_cache を設定できるようだったので試してみました。




結果は・・・


キャッシュクリアされてました!

でも、ビルドは失敗してる・・・

結局マニュアル実行
なぜだ…という気持ちを投げ捨てて、結局マニュアル実行しました。。。





結果は・・・


成功した!?
なぜだ・・・

まとめ
ビルドの結果はともかく、
Buddy でキャッシュをクリアしてビルドする場合は、以下の3つの方法が取れます。

* コミットメッセージコマンド「--clear-cache」
* YAML の「auto_clear_cache: true」
* マニュアル実行時の「Clear cache before running」

もやっとしますが、今回は以上です。
では。

2019年1月8日火曜日

[Azure Pipelines] Ubuntu 環境でコアダンプを成果物に保存するようにした

マトリックスを組んだものの、テストが Abort (core dump) してしまっていたので原因の解明のため、コアダンプを成果物として保存するようにした話です。
(コアダンプを出力するあたりは Azure Pipelines は全く関係なく、関係するのは成果物の保存くらいですが)



ulimit -c unlimited
まずは、デフォルトで
core file size          (blocks, -c) 0
になっているので ulimit -c unlimited しておきます。

apport
環境によっては ulimit の設定だけで core ファイルが出ますが、azure pipelines の ubuntu 環境はそうではありませんでした。
apport が有効になっているため、ulimit -c unlimited だけでは core dump ファイルは出力されません。
方法としては、2つ考えられます。

* apport を使わないようにする
* apport でファイル出力されるようにする

最初は /proc/sys/kernel/core_pattern を書き換えて、apport を使わないようにする方法をやってみたのですが、設定を書き換えて再起動する必要があるようだったので apport 有効な状態でファイルが出力されるようにしました。

ファイル出力されるように設定する
まずは、ファイルが出力されるようにします。
apport は通常非パッケージプログラムのクラッシュは、ダンプをファイル出力しないようです。
つまり、CI で実行されるテストは非パッケージプログラムなので、ファイルが出力されてなかったわけですね。
ubuntu - How to change apport default behaviour for non-packaged application crashes? - Stack Overflow
~/.config/apport/settings に以下の設定を書き込みます。
mkdir -p ~/.config/apport/
    echo [main] > ~/.config/apport/settings
    echo unpackaged=true >> ~/.config/apport/settings
これにより、/var/crash フォルダにダンプが出力されるようになります。

Unpack
つづいて、gdb で解析可能なようにファイルを unpack します。
apport が出力クラッシュダンプファイルは、素に出力した core dump ファイルのように gdb に食わせることができないためです。
apport - How does one get a hold of the details regarding a system/application crash - Ask Ubuntu

これには apport-unpack を使えばよいのですが、以下のエラーが出てうまくいきませんでした。
Traceback (most recent call last):
  File "/usr/bin/apport-unpack", line 73, in 
    pr.extract_keys(f, bin_keys, dir)
  File "/usr/lib/python3/dist-packages/problem_report.py", line 270, in extract_keys
    [item for item, element in b64_block.items() if element is False])
ValueError: ['UserGroups'] has no binary content
検索してもスマートな解決方法が見つからなかったので、 UserGroups に値を付け加えて unpack するようにして回避しました。
mkdir test/coredump
    for file in `\find /var/crash/ -name '*.crash'`; do sudo sed -i -e "s/UserGroups:\s*$/UserGroups:iutest/g" $file ; done
    for file in `\find /var/crash/ -name '*.crash'`; do apport-unpack $file test/coredump/`basename $file .crash` ; done

これで test/coredump フォルダにクラッシュダンプが保存されてるようになったので、あとは成果物として保存します。

成果物を保存する
ようやく Azure Pipelines の設定です。(といってもとっても簡単なのでほとんど書くことありませんが)
- task: PublishPipelineArtifact@0
  condition: failed()
  inputs:
    artifactName: 'core dump'
    targetPath: 'test/coredump'

成果物の保存には「PublishPipelineArtifact」タスクを使います。
artifactName に成果物の名前と targetPath にダンプを出力したディレクトリを指定します。
クラッシュする=ジョブの失敗なので、 condition: failed() も指定しておきましょう。

これで成果物が保存されます。
成果物は右上の「…」メニューに「Artifacts」があるので、そこに artifactName で指定した名前がリストアップされます。


保存した成果物を開くと「Artifacts explorer」が開くのでそこから成果物をダウンロードしてくることができます。


コアダンプの解析
CoreDump ファイルをダウンロードしたら、自前の Ubuntu docker コンテナを起動して、以下のコマンドを実行。
gdb ./all_tests CoreDump
(gdb) bt

iutest::detail::IOutStream::Printf で落ちてるのがわかりました。
これをもとにちゃんと修正することができました。よかったよかった。
-DIUTEST_HAS_VARIADIC_TEMPLATES=0 failed test · Issue #131 · srz-zumix/iutest

最後に
Ubuntu でのコアダンプの出力にだいぶ手間取りましたが、よく使うものがタスク化されていて、コンディションで条件付けもしやすかったので、パイプラインの構築は簡単でした。
これで、テストがクラッシュしたときに、より調査しやすいパイプラインになったかなと思います。

Azure Pipelines さんこれからもよろしくお願いしますmm