GitLab CI/CDパイプラインで生成されたファイルをGitLabのリポジトリーにコミット(Push)し直す方法

毎度、ググっても出てこない小ネタを取り扱っております。
本記事は個人的な見解であり、筆者の所属するいかなる団体にも関係ございません。

:::note info
本記事では、git push 時にSSH鍵を使っておりますが、GitLabのAccess Tokenを使った方が良い場合もありますので下記の記事も参考にしてください。

> GitlabのCI/CDパイプラインでcommitを追加する
> https://zenn.dev/offiter/articles/gitlab_cicd_add_commit
:::

~~GitLab CI/CDパイプラインを使っていて、変更内容をGitLabのリポジトリに戻すということを考えていましたが、やらなくなってしまいました。せっかく調べたので供養しておこうと思ってここに書き記しておきます~~ と思っていたのですが、結局ちゃんと使いました。

# GitLab CI/CDパイプラインを使って、Gitリポジトリに対する変更内容をGitLabのgitリポジトリに戻す

GitLabパイプラインはとても便利なのでついつい何でも動かしたくなってしまうのです。
GitLabパイプラインからGitLabパイプラインを動かしたGitリポジトリを修正しよう等と思ったのが事の発端です。
＃まあ、そういうこともあるのではないかと。

＜後で図をいれる＞

GitLab CI/CDはDockerで動いていて、.gitlab-ci.ymlファイルのあるリポジトリをDocker内で
git cloneしてから動きます。なので、git add、git commit、git pushすればいいじゃん？
って思うと思いますが、残念ながらそうはいきません。
なぜなら、git cloneされるときのリポジトリのURLへの接続アカウントがリードオンリーだからです。

対応方法は、いくつか方法があるようです。

1. GitLabの自分のアカウントにPersonal Access Tokenを作成して、そのTokenで接続する
2. SSHの公開鍵を作成し、GitLabの自分のアカウントに割り当てし、その鍵で接続する
3. SSHの公開鍵を作成し、プロジェクトにDeploy Keyとして登録して、その鍵で接続する

なるべく個人のアカウントに紐付けたくない(自分がプロジェクトから抜けたらCIが動かなくなったとか
非常に困る)ので、今回は3を選択しました。

手順を書いておきます。

## 1. SSH鍵を作成します

https://docs.gitlab.com/ce/ssh/README.html#generating-a-new-ssh-key-pair
を見て鍵を作成して下さい。パスワードは付けないで下さい。

公開鍵をコピーしておきます。

## 2. プロジェクトのリポジトリのDeploy KeyにSSH公開鍵を登録します

プロジェクトの「設定」--->「リポジトリ」にいきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/7149f019-043d-3137-cfb6-42183c742943.png)

1でコピーしておいたSSHの公開鍵以下の「key」に貼ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/a5153240-5aac-d699-a02e-502919264a47.png)

「タイトル」は何でもいいでしょう。

「Write access allowed」にチェックを入れます。

「Add Key」を押します。

## 3. プロジェクトのVariableにSSH秘密鍵を登録します

プロジェクトの「設定」--->「CI / CD」にいきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/a24dcbc0-9bcc-4101-dca8-e1326e51509b.png)

Variablesを開きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/b95c1767-1bc8-ef47-1c0d-e42709264b08.png)


1のSSH鍵で秘密鍵をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/38a7bb57-0107-2b73-e971-52cd882a14a1.png)
「Key」を「SSH_PRIVATE_KEY」とします。
「value」に1のSSH秘密鍵を貼り付けます。
＃Variableではなくて、Fileにするという手もあります。

## 4. .gitlab-ci.yml を書く

以下のようなコマンドを.gitlab-ci.ymlに書きます。

```yml:gitlab-ci.yml
stages:
  - writeback-gitrepo

image: ubuntu:20.04

writeback-gitrepo:
  script:
    - apt-get update && apt-get install -y curl openssh-client git jq
    - eval $(ssh-agent -s)
    - printenv SSH_PRIVATE_KEY | ssh-add -
    - git config --global user.name "hogehoge-runner@example.jp"
    - git config --global user.email "hogehoge-runner@example.jp"
    - git remote set-url --push origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git
    - git checkout $CI_COMMIT_REF_NAME
    - echo "なにかgitリポジトリ内のファイルを変更する処理を書く"
    - git add ./commit-file-example.txt
    - ret=$(git status| sed -ne 's|.*\(clean\)|\1|p')
    - if [ -z $ret ];then
    -   git commit -m '[ci skip] Push by GitLab runner'
    -   git -c core.sshCommand="ssh -oStrictHostKeyChecking=no" push origin
    - fi
```

上記の.gitlab-ci.ymlの注意点がいくつかあるので解説しておきます。

### 1. ~~.sshディレクトリが必要なの？~~

~~.sshは、known_hostsファイルを入れるためだけに作っています。~~
~~もしかしたら、SSHのオプションで回避することができるかも。~~

gitオプションに`-c core.sshCommand="ssh -oStrictHostKeyChecking=no"`を追加して不要になりました。

@sho0405 さんの以下のコメントを参考にしました。
ありがとうございます。

https://qiita.com/ynott/items/8cb3b3995cb41ca78437#comment-4fb389210cc6a953e970

### 2. sshコマンドは必要？

はい、必要です。
image: で指定するDockerイメージファイルに入れておきましょう。
同様にgitコマンドも必要です。ssh-agentはsshパッケージに入っています。

### 3. git remote set-urlて何してる？

git cloneしたときにoriginとして入っているリモートURLが通常はhttps://hogehoge なので、
それをSSH接続に変更するために上書きしています。

### 4. なぜ、checkoutしているの？

Runnerでgit cloneされるのはブランチではなくコミットに対してです。
そのため、ワークツリーはDetached HEAD状態になります。
そのままでは、コミットしてもDetached HEADでコミットできないので作業のワークツリーをコミットしたブランチに戻します。
 `$CI_COMMIT_REF_NAME` には、コミットを実行したブランチ名が入ります。
mainブランチに対してコミットしたら、$CI_COMMIT_REF_NAMEは`main`です。

### 5. git statusの戻り値で条件分岐してgit commitしているのはなぜ？

ワーキングコピーがcleanな状態でgit commitするとエラーが出てgitlab ciがfailと認識してそこで終了してしまいます。
git status でワーキングコピーがクリーンなときにはcommitしないようにしてあります。pushも同様です。

### 6. コミットメッセージの[ci skip]ってなに？

コミットメッセージに[ci skip]が入っているとGitLabパイプラインがそのコミットに対しては動きません。
これを入れておかないと、自分がコミットし直したトリガーにより再度パイプラインが走って無限ループ状態になります。

ブランチを切るなどの操作でも対応可能です。

## 参考資料
A gitlab-ci config to deploy to your server via ssh
https://medium.com/@hfally/a-gitlab-ci-config-to-deploy-to-your-server-via-ssh-43bf3cf93775

Push Git Tag from GitLab Runner | thoean.com
https://thoean.com/push-git-tag-from-gitlab-runner/

GitBot – automating boring Git operations with CI | GitLab
https://about.gitlab.com/2017/11/02/automating-boring-git-operations-gitlab-ci/

ssh - GitLab CI denies access to push using a deploy key with write access - Stack Overflow
https://stackoverflow.com/questions/55224376/gitlab-ci-denies-access-to-push-using-a-deploy-key-with-write-access

GitLabCIの結果をGitHubにコミットする - kawaken's blog
http://kawaken.hateblo.jp/entry/2018/09/20/210542

Using SSH keys with GitLab CI/CD | GitLab
https://docs.gitlab.com/ee/ci/ssh_keys/

GitLab and SSH keys | GitLab
https://docs.gitlab.com/ce/ssh/README.html

gitlab-ci-git-push/git-push at master · IlyaSemenov/gitlab-ci-git-push
https://github.com/IlyaSemenov/gitlab-ci-git-push/blob/master/git-push

Push files to gitlab-ci via CI runner - Stack Overflow
https://stackoverflow.com/questions/40122780/push-files-to-gitlab-ci-via-ci-runner