gitlab-ciでphp-cs-fixerを自動コミットする

※2018-08-16 変数の保護について修正しました

push時にphp-cs-fixerをかけて、修正箇所があれば自動コミットする方法です。

大まかな処理内容

  1. feature/*ブランチまたはfix/*ブランチに対して処理する。masterに対しては行わない。
  2. pushされたらphp-cs-fixerをかけ、変更点があればそれをcommit/pushして、後続処理は行わない(※pushした内容で新たにパイプラインが実行される)
  3. php-cs-fixerによる変更がなければ後続処理(自動テストなど)を行う

.gitlab-ci.ymlの内容

image: php:7.2-alpine

stages:
  - cs-fixer
  - test

cs-fixer:
  stage: cs-fixer
  only:
    - /^feature\//
    - /^fix\//
  before_script:
    - apk add git openssh-client
    - eval $(ssh-agent -s)
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "$GIT_SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  script:
    - php php-cs-fixer fix src
    - if [ $(git status --porcelain | wc -l) -eq 0 ] ; then echo "php-cs-fixerチェックOK" ; exit ; fi
    - git config --global user.email "gitlab-ci@example.com"
    - git config --global user.name "gitlab-ci"
    - git add .
    - git commit -m "auto php-cs-fixer"
    - git push git@gitlab.com:noldor/sandbox.git HEAD:${CI_COMMIT_REF_NAME}
    - echo "php-cs-fixerによる修正をpushしました"
    - exit 1

php-lint:
  stage: test
  script:
    - find . -name '*.php' -print0 | xargs -0 -n1 php -l

仕掛けとしては、カレントディレクトリに対象コミットがチェックアウトされた状態でdockerが起動されているので、これを変更してpushしてしまうというものです。
ポイントとしては

  • ジョブ上ではssh-agentが実行されていない状態なのでインストール、実行する
  • push先を明示する(デフォルトではhttps://〜〜になっている)

ssh-agentの仕掛け方は公式マニュアル https://docs.gitlab.com/ee/ci/ssh_keys/ に記載されているものがベースです。StrictHostKeyChecking noはやりすぎかもしれません。

中に書いてある環境変数CI_COMMIT_REF_NAMEは自動でセットされるもので、この場合ではブランチ名です。
GIT_SSH_PRIVATE_KEYは自分で設定する変数で、設定画面で秘密鍵を設定しておきます。この鍵を使って自動修正をリポジトリにpushします。

gitlabでの設定

まず、パイプライン実行時に渡す変数を設定します。

次に公開鍵を設定します。ユーザに紐付けると統計情報に影響があるため、デプロイキーとして設定します。デプロイキーは書き込み権限ありで設定します。

これでリポジトリにpushする準備ができました。

ブランチを切ってpushする

上記の.gitlab-ci.ymlと、php-cs-fixerに修正されるようなソースをcommit/pushします。
次のようなコードを書きました。

<?php

function get_wild() {
    return 'wild';
}

function get_wild_and_tough() {
    return ['wild', 'tough'];
}

開きカッコの位置が修正対象で、これはpsr-2では「改行してから開く」ということになっています。

パイプラインは2つ実行されました。

失敗しているのがphp-cs-fixerによる修正、直後に実行されているのがその修正コミットによるものです。
コミットログはこんなかんじです。

また、1つまえのmasterブランチに対する実行は1つしかありませんが、.gitlab-ci.ymlに書いたonlyの指定によりcs-fixerジョブが対象外となり、php-lintジョブだけが実行されたものです。

自動コミットの方法は今後変わるはず

どこか忘れましたが、gitlab.comのどこかに「gitlab-ci上での自動コミットのサポート」というissuesがあったはずです。ここで行っている処理は多少強引なので、正式にサポートされたならそれに追従したほうが素直な書き方になるでしょう。

2018-08-16修正

公開時は「秘密鍵の変数を保護、合わせてfeature,fixブランチも保護」と書いていましたが、コメントがあり変数を保護しないように修正しました。ご指摘ありがとうございました。

おまけ:パイプラインのログ

Running with gitlab-runner 11.2.0-rc2 (1644837a)
on docker-auto-scale 72989761
Using Docker executor with image php:7.2-alpine ...
Pulling docker image php:7.2-alpine ...
Using docker image sha256:f7b790705e50fd8bc9ea1ebb09d15e2c60521880dd924157768239bc1f75c6b0 for php:7.2-alpine ...
Running on runner-72989761-project-7953850-concurrent-0 via runner-72989761-srm-1534347652-cd1cc299...
Cloning repository...
Cloning into '/builds/noldor/sandbox'...
Checking out 006264cb as feature/add-get-wild-and-tough...
Skipping Git submodules setup
$ apk add git openssh-client
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
(1/5) Installing expat (2.2.5-r0)
(2/5) Installing pcre2 (10.30-r0)
(3/5) Installing git (2.15.2-r0)
(4/5) Installing openssh-keygen (7.5_p1-r8)
(5/5) Installing openssh-client (7.5_p1-r8)
Executing busybox-1.27.2-r11.trigger
OK: 32 MiB in 34 packages
$ eval $(ssh-agent -s)
Agent pid 12
$ mkdir -p ~/.ssh
$ chmod 700 ~/.ssh
$ echo "$GIT_SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - &gt; /dev/null
Identity added: (stdin) ((stdin))
$ [[ -f /.dockerenv ]] &amp;&amp; echo -e "Host *\n\tStrictHostKeyChecking no\n\n" &gt; ~/.ssh/config
$ php php-cs-fixer fix src
Loaded config default.
1) src/index.php

Fixed all files in 0.006 seconds, 10.000 MB memory used
$ if [ $(git status --porcelain | wc -l) -eq 0 ] ; then echo "php-cs-fixerチェックOK" ; exit ; fi
$ git config user.email "gitlab-ci@example.com"
$ git config user.name "gitlab-ci"
$ git add .
$ git commit -m "auto php-cs-fixer"
[detached HEAD 0c0cfba] auto php-cs-fixer
2 files changed, 5 insertions(+), 2 deletions(-)
create mode 100644 .php_cs.cache
$ git push git@gitlab.com:noldor/sandbox.git HEAD:${CI_COMMIT_REF_NAME}
Warning: Permanently added 'gitlab.com,35.231.145.151' (ECDSA) to the list of known hosts.
remote:
remote: To create a merge request for feature/add-get-wild-and-tough, visit:
remote: https://gitlab.com/noldor/sandbox/merge_requests/new?merge_request%5Bsource_branch%5D=feature%2Fadd-get-wild-and-tough
remote:
To gitlab.com:noldor/sandbox.git
006264c..0c0cfba HEAD -&gt; feature/add-get-wild-and-tough
$ echo "php-cs-fixerによる修正をpushしました"
php-cs-fixerによる修正をpushしました
$ exit 1
ERROR: Job failed: exit code 1

3 件のコメント

  1. hiroponz 返信

    興味深い記事を執筆して頂きありがとうございます。

    「保護ブランチ」について、使用方法に誤りがあるので修正したほうが良いと思います。
    “feature/*”を「保護ブランチ」にすると、ブランチの削除ができなくなるで通常は行わない方が良いです。また、「保護ブランチ」にしてもスクリプトから保護した変数を読み取ることができるので、今回のケースではセキュリティを向上させる効果もほとんどありません。

    保護した変数のユースケースとしては、プロダクション環境のデプロイキーをMaintainer権限のユーザーのみに公開し、Developer権限のユーザーには秘匿したい場合などがあります。

    • takekoshi 投稿者返信

      コメントありがとうございます。
      記事を書いた動機がclosedプロジェクトに対してだったので「とりあえず保護しとこう」で進めましたが、openなプロジェクトだとご指摘の通り誤りになってよろしくないですね。
      .gitlab-ci.ymlで秘密鍵をechoするコードをfeatureブランチにpushしてしまえばログに出てしまうのに、保護されていると勘違いしそうです。
      これから記事を修正します。

    • takekoshi 投稿者返信

      変数保護しないように修正、合わせてブランチ保護のくだりを削除しました。ご指摘ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください