summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/content/posts/2024-04-21
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-04-09 20:28:28 +0900
committernsfisis <nsfisis@gmail.com>2025-04-09 22:24:37 +0900
commit1e2f2e68286ce018c57945ab3cdcbaaf484377ee (patch)
tree87f160362cafc79c7e45ebb3415fe6ef9524151c /vhosts/blog/content/posts/2024-04-21
parentdbc3e0dfd893435f31cca39873f7ba9bf13b93a6 (diff)
downloadnsfisis.dev-1e2f2e68286ce018c57945ab3cdcbaaf484377ee.tar.gz
nsfisis.dev-1e2f2e68286ce018c57945ab3cdcbaaf484377ee.tar.zst
nsfisis.dev-1e2f2e68286ce018c57945ab3cdcbaaf484377ee.zip
feat(blog/content): convert from .ndoc to .dj
Diffstat (limited to 'vhosts/blog/content/posts/2024-04-21')
-rw-r--r--vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj155
-rw-r--r--vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc180
2 files changed, 155 insertions, 180 deletions
diff --git a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj
new file mode 100644
index 00000000..9872d284
--- /dev/null
+++ b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj
@@ -0,0 +1,155 @@
+---
+[article]
+uuid = "a4c326a6-5ffe-450c-abf2-45833c5efb6a"
+title = "【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている"
+description = "GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。"
+tags = [
+ "ci-cd",
+ "gitlab",
+]
+
+[[article.revisions]]
+date = "2022-11-17"
+remark = "デジタルサーカス株式会社の社内記事として公開"
+isInternal = true
+
+[[article.revisions]]
+date = "2024-04-21"
+remark = "ブログ記事として一般公開"
+---
+::: note
+この記事は、2022-11-17 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
+:::
+
+ハマったのでメモ。
+
+{#background}
+# 前提
+
+{#gitlab-ci-cd}
+## GitLab CI/CD について
+
+GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
+
+例:
+
+```yaml
+hello-world:
+ stage: test
+ image: alpine:latest
+ script:
+ - 'echo "Hello, World!"'
+ rules:
+ - if: '$CI_MERGE_REQUEST_IID'
+ when: always
+```
+
+ここで、`script` に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
+
+では、次のようなケースだとどうなるか。
+
+```yaml
+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-option}
+## `pipefail` オプションについて
+
+前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、`pipefail` オプションを有効にする。
+
+```bash
+# On にする
+set -o pipefail
+# Off にする
+set +o pipefail
+```
+
+こうすると、パイプ全体が失敗するようになる。
+この設定は、デフォルトだと off になっている。
+
+{#problem}
+# 発生した問題
+
+次のような GitLab CI/CD ジョブが失敗してしまった。
+
+```yaml
+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 になっているのか。
+
+{#where-pipefail-is-enabled}
+# どこで `pipefail` が on になるか
+
+`.gitlab-ci.yml` で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。
+そう仮説を立てて [GitLab Runner のリポジトリ](https://gitlab.com/gitlab-org/gitlab-runner) を調査したところ、 [ソースコード中の以下の箇所](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/c75da0796a0e3048991dccfdf2784e3d931beda4/shells/bash.go#L276) で `set -o pipefail` していることが判明した (コメントは筆者による)。
+
+```go
+// pipefail オプションが存在しない環境にも対応するため、
+// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
+buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
+```
+
+{#how-to-solve}
+# どのように解決するか
+
+通常の Bash スクリプトを書く場合と同様に、`pipefail` が on になっていては困る場所だけ off にしてやればよい。
+
+```yaml
+ 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
+```
+
+{#remarks}
+# 備考
+
+なお、上述した実装ファイルは `shells/bash.go` だが、`alpine:latest` の例でもそうであったように、シェルが `sh` である場合にも適用される。
diff --git a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc
deleted file mode 100644
index d65fffb5..00000000
--- a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc
+++ /dev/null
@@ -1,180 +0,0 @@
----
-[article]
-uuid = "a4c326a6-5ffe-450c-abf2-45833c5efb6a"
-title = "【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている"
-description = "GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。"
-tags = [
- "ci-cd",
- "gitlab",
-]
-
-[[article.revisions]]
-date = "2022-11-17"
-remark = "デジタルサーカス株式会社の社内記事として公開"
-isInternal = true
-
-[[article.revisions]]
-date = "2024-04-21"
-remark = "ブログ記事として一般公開"
----
-<article>
- <note>
- この記事は、2022-11-17 に<a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
- </note>
- <p>
- ハマったのでメモ。
- </p>
- <section id="background">
- <h>前提</h>
- <section id="gitlab-ci-cd">
- <h>GitLab CI/CD について</h>
- <p>
- GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
- </p>
- <p>
- 例:
- </p>
- <codeblock language="yaml" filename=".gitlab-ci.yml">
- <![CDATA[
- hello-world:
- stage: test
- image: alpine:latest
- script:
- - 'echo "Hello, World!"'
- rules:
- - if: '$CI_MERGE_REQUEST_IID'
- when: always
- ]]>
- </codeblock>
- <p>
- ここで、<code>script</code> に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
- </p>
- <p>
- では、次のようなケースだとどうなるか。
- </p>
- <codeblock language="yaml" filename=".gitlab-ci.yml">
- <![CDATA[
- hello-world:
- stage: test
- image: alpine:latest
- script:
- - 'exit 1 | exit 0'
- rules:
- - if: '$CI_MERGE_REQUEST_IID'
- when: always
- ]]>
- </codeblock>
- <p>
- 失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
- </p>
- </section>
- <section id="pipefail-option">
- <h><code>pipefail</code> オプションについて</h>
- <p>
- 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、<code>pipefail</code> オプションを有効にする。
- </p>
- <codeblock language="bash">
- <![CDATA[
- # On にする
- set -o pipefail
- # Off にする
- set +o pipefail
- ]]>
- </codeblock>
- <p>
- こうすると、パイプ全体が失敗するようになる。
- この設定は、デフォルトだと off になっている。
- </p>
- </section>
- </section>
- <section id="problem">
- <h>発生した問題</h>
- <p>
- 次のような GitLab CI/CD ジョブが失敗してしまった。
- </p>
- <codeblock language="yaml" filename=".gitlab-ci.yml">
- <![CDATA[
- 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
- ]]>
- </codeblock>
- <p>
- <code>grep</code> コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、<code>pipefail</code> が on になっていると、このジョブは失敗する。
- 現在の <code>pipefail</code> がどうなっているか確かめるため <code>set +o</code> で全オプションを出力させたところ、<code>pipefail</code> が on になっていた。
- </p>
- <p>
- しかし、先述したように Bash における <code>pipefail</code> のデフォルト値は off のはずだ。
- 実際に、ローカルで <code>alpine:latest</code> を動かしてみたところ、
- </p>
- <codeblock>
- <![CDATA[
- $ 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
- ]]>
- </codeblock>
- <p>
- 確かに <code>pipefail</code> は無効になっている。
- </p>
- <p>
- なぜスクリプト内で <code>set -o pipefail</code> しているわけでもないのに <code>pipefail</code> が on になっているのか。
- </p>
- </section>
- <section id="where-pipefail-is-enabled">
- <h>どこで <code>pipefail</code> が on になるか</h>
- <p>
- <code>.gitlab-ci.yml</code> で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。
- そう仮説を立てて <a href="https://gitlab.com/gitlab-org/gitlab-runner">GitLab Runner のリポジトリ</a> を調査したところ、<a href="https://gitlab.com/gitlab-org/gitlab-runner/-/blob/c75da0796a0e3048991dccfdf2784e3d931beda4/shells/bash.go#L276">ソースコード中の以下の箇所</a> で <code>set -o pipefail</code> していることが判明した (コメントは筆者による)。
- </p>
- <codeblock language="go">
- <![CDATA[
- // pipefail オプションが存在しない環境にも対応するため、
- // 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
- buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
- ]]>
- </codeblock>
- </section>
- <section id="how-to-solve">
- <h>どのように解決するか</h>
- <p>
- 通常の Bash スクリプトを書く場合と同様に、<code>pipefail</code> が on になっていては困る場所だけ off にしてやればよい。
- </p>
- <codeblock language="yaml" diff="true" filename=".gitlab-ci.yml">
- <![CDATA[
- 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
- ]]>
- </codeblock>
- </section>
- <section id="remarks">
- <h>備考</h>
- <p>
- なお、上述した実装ファイルは <code>shells/bash.go</code> だが、<code>alpine:latest</code> の例でもそうであったように、シェルが <code>sh</code> である場合にも適用される。
- </p>
- </section>
-</article>