-
-

更新履歴

-
    -
  1. - : デジタルサーカス株式会社の社内記事として公開 -
  2. -
  3. - : ブログ記事として一般公開 -
  4. -
-
-
-
- NOTE -
-
-

- この記事は、2022-11-17 に デジタルサーカス株式会社 の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。 -

-
-
-

- ハマったのでメモ。 -

-
-

前提

-
-

GitLab CI/CD について

-

- GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。 -

-

- 例: -

-
-
hello-world:
-  stage: test
-  image: alpine:latest
-  script:
-    - 'echo "Hello, World!"'
-  rules:
-    - if: '$CI_MERGE_REQUEST_IID'
-  when: always
-
-

- ここで、script に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。 -

-

- では、次のようなケースだとどうなるか。 -

-
-
hello-world:
-  stage: test
-  image: alpine:latest
-  script:
-    - 'exit 1 | exit 0'
-  rules:
-    - if: '$CI_MERGE_REQUEST_IID'
-  when: always
-
-

- 失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。 -

-
-
-

pipefail オプションについて

-

- 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。 -

-
-
# On にする
-set -o pipefail
-# Off にする
-set +o pipefail
-
-

- こうすると、パイプ全体が失敗するようになる。この設定は、デフォルトだと off になっている。 -

-
-
-
-

発生した問題

-

- 次のような GitLab CI/CD ジョブが失敗してしまった。 -

-
-
hoge:
-  stage: test
-  image: alpine:latest
-  script:
-    - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
-  rules:
-    - if: '$CI_MERGE_REQUEST_IID'
-  when: always
-
-

- grep コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、pipefail が on になっていると、このジョブは失敗する。現在の pipefail がどうなっているか確かめるため set +o で全オプションを出力させたところ、pipefail が on になっていた。 -

-

- しかし、先述したように Bash における pipefail のデフォルト値は off のはずだ。実際に、ローカルで alpine:latest を動かしてみたところ、 -

-
-
$ docker run --rm alpine:latest sh -c "set +o"
-set +o errexit
-set +o noglob
-set +o ignoreeof
-set +o monitor
-set +o noexec
-set +o xtrace
-set +o verbose
-set +o noclobber
-set +o allexport
-set +o notify
-set +o nounset
-set +o vi
-set +o pipefail
-
-

- 確かに pipefail は無効になっている。 -

-

- なぜスクリプト内で set -o pipefail しているわけでもないのに pipefail が on になっているのか。 -

-
-
-

どこで pipefail が on になるか

-

- .gitlab-ci.yml で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。そう仮説を立てて GitLab Runner のリポジトリ を調査したところ、 ソースコード中の以下の箇所set -o pipefail していることが判明した (コメントは筆者による)。 -

-
-
// pipefail オプションが存在しない環境にも対応するため、
-// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
-buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
-
-
-
-

どのように解決するか

-

- 通常の Bash スクリプトを書く場合と同様に、pipefail が on になっていては困る場所だけ off にしてやればよい。 -

-
-
 hoge:
-   stage: test
-   image: alpine:latest
-   script:
-+    - 'set +o pipefail'
-     - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
-+    - 'set -o pipefail' # この例の場合、ここで終わりなので戻さなくてもよい
-   rules:
-     - if: '$CI_MERGE_REQUEST_IID'
-   when: always
-
-
-
-

備考

-

- なお、上述した実装ファイルは shells/bash.go だが、alpine:latest の例でもそうであったように、シェルが sh である場合にも適用される。 -

-
-