令和元年冬 犬は駆け廻り 猫は円くなり 私はCIに目を回す

# 0. 序文

本稿は [TECOTEC Advent Calendar 2019](https://qiita.com/advent-calendar/2019/tecotec) の21日目の記事です。

:raising_hand_tone1:「エンジニアがすなる技術ブログといふものを、私もしてみむとしてするなり。はてなブログ、君に決めた！」
:smirk_cat:「そういえば、小説の原稿をGit管理すると、編集点や更新履歴が共有できて、校正・編集が捗るって聞いたことがあるよ」
:angel:「Gitを使うなら……投稿まで自動化しては……どうでしょう……捗りマックスですよ……」
:thumbsup_tone1:「いいね！それ採用」

というわけでGitHub初心者が見果てぬ夢を追いかけて自動化の塔を目指すまでの物語です。

# X. 本稿の内容を3行で

- ブログ記事をGit管理して校正・改稿を可視化したく
- Git使うなら投稿まで自動化できたらいいなとCircleCIに手を出し
- 必要なパッケージがインストールできずに頓挫した

今のところ先生にもなれないしくじり話です、ご容赦ください。
同じような躓き方をしてしまった時に、少しでもご参考になれば幸いです。

# 1. 訓練

## 1-1. 調査

:speaking_head:「まずは冒険に必要な道具を確認しましょう」
:relieved:「『はてなブログ Git 管理』でググります」

[このブログの内容を GitHub で管理するようにした - えいのうにっき](https://blog.a-know.me/entry/2018/03/03/232350)

:speaking_head:「あなたは【blogsync】という道具が有用であることを知りました。はてなブログの投稿記事を取得する、新しい記事を作成する、既にある記事を更新する――以上のことができます。Go言語で書かれた、AtomPubのラッパーのようです」
:smirk:「グッド（Go触ったことないけど）。関連記事にもっと詳しい情報がないか調べます」

[コード化したはてなブログリポジトリの更新を CircleCI 2.0 で自動化した - えいのうにっき](https://blog.a-know.me/entry/2018/03/04/215345)

:speaking_head:「あなたは【CircleCI】でblogsyncを動かすという方策を見つけました。」
:innocent:「【blogsync】と【CircleCI】で自動化ね、完全に理解した（CircleCI使ったことないけど）」

## 1-2. 地図

:speaking_head:「それでは、冒険の地図を作りましょう」
:confused:「ブログを管理するGitHubリポジトリを作って、それをCircleCIに登録して、CircleCIの中ではblogsyncを動かして……」
:speaking_head:「blogsyncで《記事作成》と《記事更新》を行う場合、ファイル名を指定する必要があるようです」
:relaxed:「じゃあ、pull requestでのmasterブランチとの差分を取得して、それぞれのファイルについて`post`と`push`をする感じで……できた！」

![blogsync.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220830/6bef87d1-832b-c306-c89d-f5d2d5c3bae0.png)

## 1-3. 準備

:sunglasses:「まずは自分の家（ローカル）で【blogsync】を入手し、試してみたいと思います」
:speaking_head:「よいでしょう。構築環境を決めるので、1d6を振ってください……はい、あなたの環境はWindowsなので、【Chocolatey】を導入するところからです」

[Windows10でChocolateyを使ってみた - Qiita](https://qiita.com/hideki0145/items/e760eebcc5019275ae6a)

:yum:「チョコ美味しいなり。で、次はGoのインストール……`$ choco install golang`を唱えます」
:speaking_head:「では、2d6を振ってください」
:unamused:「えっ、成功判定？ こんなところで失敗することなんてあるの……？」コロコロ……

2→ [9-1.](#9-1-chocolateyを使ったインストールに失敗する)に進む
3以上→ 1-4.に進む

## 1-4. 調達

:speaking_head:「Go環境を導入して、【blogsync】を入手する準備が整いました」
:smile:「唱えます。`$ go get github.com/motemen/blogsync`」
:speaking_head:「2d6 + 《Go知識》ボーナスで12以上で成功です」
:sweat_smile:「高っ!? ボーナス0なんですけど。インストールするだけですよね……？」

11以下→ [9-2.](#9-2-blogsyncのインストールに失敗するcli)に進む
12→ 1-5.に進む

## 1-5. 実践

:speaking_head:「【blogsync】を手に入れました」
:kissing_closed_eyes:「やったね！ではGitリポジトリを作成してルートを決め、configファイルを作ります」
:speaking_head:「成功です」
:relieved:「ここはあっさりなんですね」

[motemen/blogsync - GitHub](https://github.com/motemen/blogsync)

```yaml:.config/blogsync/config.yaml
[はてなブログのURL]:
    username: [はてなアカウントのID]
    password: [はてなブログの"詳細設定"の"AtomPub"にあるAPIキー]
    local_root: [Gitリポジトリをcloneしたディレクトリ]
    omit_domain: [local_rootの階層を深くしたくなければtrue]
```

:wink:「早速サンプルで用意したはてなブログを引っ張ってみます。`$ blogsync pull [はてなブログのURL]`」

```powershell
PS C:\Users\***\go\src\github.com\motemen\blogsync > .\blogsync pull [ブログURL]
    GET ---> https://blog.hatena.ne.jp/[はてなID]/[ブログURL]/atom/entry
    200 <--- https://blog.hatena.ne.jp/[はてなID]/[ブログURL]/atom/entry
  fresh remote=2019-12-21 00:00:00 +0900 JST > local=0001-01-01 00:00:00 +0000 UTC
  store [local_rootパス]\entry\2019\12\20\000000.md
```

:speaking_head:「成功です。12/20に作成していた下書き状態の記事を.md形式で取得することができました」

# 2. 円環

## 2-1. 導入

:speaking_head:「では、自動化の塔に挑みましょう」
:sweat_smile:「展開が早い……いや実際にはここまでだいぶ死に戻りを繰り返した気がするけれども……。まぁローカルで成功したことですし、同じようにすればできるでしょ。【CircleCI】をGitHubアカウントで利用開始、リポジトリを登録します」
:speaking_head:「成功です」
:expressionless:「さすがにそれはね……。では、[調査](#1-1-調査)で獲得した資料を参照して、`.circleci/config.yaml`を作成し、masterブランチにpull requestを送ります」
:speaking_head:「**.yaml**ファイルの作成でよいですね？」

YES→ [9-4.](#9-4-circleciのconfigファイルが読み込めない)に進む
NO→ 2-2.に進む

## 2-2. 調整
:fearful:「間違えました、説明書をよく読むと**.yml**と書いてあるので、`.circleci/config.yml`を作成します。記述も先の資料をお手本にしますが、ブログ名称などもCircleCIの環境変数で定義するようにします。また、blogsyncを入れた後は、とりあえず`$ blogsync pull`が動くか試してみようと思います」

```yml:.circleci/config.yml
version: 2
jobs:
  build:
    environment:
      - GOPATH: /home/circleci/go
    docker:
      - image: circleci/golang:1.12
    working_directory: /go/src/github.com/[リポジトリのパス]
    steps:
      # ここから https://blog.a-know.me/entry/2018/03/04/215345 の記述をそのまま拝借します
      # GOPATH の設定。environment だけじゃ無理っぽい？ https://qiita.com/tomiyan/items/6142113011243c5b5cd1
      - run: echo 'export PATH=${GOPATH}/bin/:${PATH}' >> $BASH_ENV
      # blogsync 用の config ファイルを置く場所の作成
      - run: mkdir -p ~/.config/blogsync
      # blogsync 用の config ファイルの書き出し。コロンをエスケープするために使ったダブルクォートを tr コマンドで無理矢理削除している
      - run: echo -e "${HATEBLO_URL}\":\"\n  username\":\" ${HATEBLO_USERNAME}\n  password\":\" ${HATEBLO_PASSWORD}\n  local_root\":\" /go/src/github.com/[リポジトリのパス]\n  omit_domain\":\" true" | tr -d \" >> ~/.config/blogsync/config.yaml
      # ここまで

      # go get github.com/motemen/blogsync がパッケージ依存の関係でエラーになる
      - run: git clone https://github.com/motemen/blogsync.git ${GOPATH}/src/github.com/motemen/blogsync
      - run: cd ${GOPATH}/src/github.com/motemen/blogsync && go build -o blogsync
      # リポジトリからチェックアウト
      - checkout

      - run:
          name: pull blog entries
          command: |
            cd ${GOPATH}/src/github.com/motemen/blogsync && ./blogsync pull ${HATEBLO_URL}
```

[9-3.](#9-3-blogsyncのインストールに失敗するパッケージ不足)に進む

## 2-3. 死闘

:cold_sweat:「……importしているパッケージをすべて`$ go get`すれば……いいんですかね……？」

```yml:.circleci/config.yml
version: 2
jobs:
  build:
    environment:
      - GOPATH: /home/circleci/go
    docker:
      - image: circleci/golang:1.12
    working_directory: /go/src/github.com/[リポジトリのパス]
    steps:
      # ここから https://blog.a-know.me/entry/2018/03/04/215345 の記述をそのまま拝借します
      # GOPATH の設定。environment だけじゃ無理っぽい？ https://qiita.com/tomiyan/items/6142113011243c5b5cd1
      - run: echo 'export PATH=${GOPATH}/bin/:${PATH}' >> $BASH_ENV
      # blogsync 用の config ファイルを置く場所の作成
      - run: mkdir -p ~/.config/blogsync
      # blogsync 用の config ファイルの書き出し。コロンをエスケープするために使ったダブルクォートを tr コマンドで無理矢理削除している
      - run: echo -e "${HATEBLO_URL}\":\"\n  username\":\" ${HATEBLO_USERNAME}\n  password\":\" ${HATEBLO_PASSWORD}\n  local_root\":\" /go/src/github.com/[リポジトリのパス]\n  omit_domain\":\" true" | tr -d \" >> ~/.config/blogsync/config.yaml
      # ここまで

      # go get github.com/motemen/blogsync がパッケージ依存の関係でエラーになるので、パッケージを個別に入れてからbuildする
      - run: go get gopkg.in/yaml.v2
      - run: go get github.com/motemen/go-colorine
      - run: go get github.com/motemen/go-loghttp
      - run: go get github.com/motemen/go-wsse
      - run: go get github.com/cpuguy83/go-md2man
      - run: go get github.com/urfave/cli

      - run: git clone https://github.com/motemen/blogsync.git ${GOPATH}/src/github.com/motemen/blogsync
      - run: cd ${GOPATH}/src/github.com/motemen/blogsync && go build -o blogsync
      # リポジトリからチェックアウト
      - checkout

      - run:
          name: pull blog entries
          command: |
            cd ${GOPATH}/src/github.com/motemen/blogsync && ./blogsync pull ${HATEBLO_URL}
```

[9-5.](#9-5-blogsyncのインストールに失敗するcli-circleci)に進む

# 9. 道場

## 9-1. Chocolateyを使ったインストールに失敗する

:speaking_head:「:skull:ファンブル:skull:です」

【現象】zipパッケージのダウンロードがタイムアウトになる
【再現率】環境による（自宅PCでは100%、別PCでは発生せず）
【原因】不明
【解決策】chocolatey.configに記載の、パッケージソースの参照場所を書き換える（`https://`を`http://`に変更する）

[Can't get Chocolatey to download files - Stack Overflow](https://stackoverflow.com/questions/22792955/cant-get-chocolatey-to-download-files)

chocolatey.configは
`C:\ProgramData\chocolatey\config`
にあるが、保護（上書き禁止）になっていることもあり、PowerShellからviで開いて編集したいところ。
Vimが入っていない場合はVimを入れるところから始めましょう。

[Powershellでvimを使う - Qiita](https://qiita.com/shuhoyo/items/d9966e12976275f20c24)

解決したら[1-3.](#1-3-準備)に戻ります。

## 9-2. blogsyncのインストールに失敗する(cli)

:speaking_head:「失敗です」

```powershell
C:\Users\***\go\src\github.com\motemen\blogsync\main.go:17:15: cannot use []cli.Command literal (type []cli.Command) as type []*cli.Command in assignment
C:\Users\***\go\src\github.com\motemen\blogsync\main.go:157:15: cannot use cli.BoolFlag literal (type cli.BoolFlag) as type cli.Flag in array or slice literal:
        cli.BoolFlag does not implement cli.Flag (Apply method has pointer receiver)
C:\Users\***\go\src\github.com\motemen\blogsync\main.go:158:17: cannot use cli.StringFlag literal (type cli.StringFlag) as type cli.Flag in array or slice literal:
        cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver)
C:\Users\***\go\src\github.com\motemen\blogsync\main.go:159:17: cannot use cli.StringFlag literal (type cli.StringFlag) as type cli.Flag in array or slice literal:
        cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver)
```

【現象】コンパイルに失敗してblogsyncをバイナリ化できない
【再現率】100%（2019/12/21現在。おそらく2019年10月以前なら0%）
【原因】main.goでimportしているCLI用パッケージ（urfave/cli）にバージョン互換性がない（v1→v2で破壊的な変化をしている）
【解決策】`$ go get`ではなく、対象のリポジトリを`$ git clone`して、それから`$ go build`する

[Cannot get package - GitHub](https://github.com/swaggo/swag/issues/556)

Windowsの場合は、`$ go build -o blogsync.exe`と、実行ファイルであることを明示して作成する必要があります。
blogsyncのパッケージ化に成功したら、[1-5.](#1-5-実践)に進みます。

## 9-3. blogsyncのインストールに失敗する(パッケージ不足)

:speaking_head:「失敗です」
:worried:「わけがわからないよ……」

```bash
$ #!/bin/bash -eo pipefail
go build -o blogsync
blogsync/broker.go:10:2: cannot find package "github.com/motemen/blogsync/atom" in any of:
	/usr/local/go/src/github.com/motemen/blogsync/atom (from $GOROOT)
	/home/circleci/go/src/github.com/motemen/blogsync/atom (from $GOPATH)
blogsync/log.go:6:2: cannot find package "github.com/motemen/go-colorine" in any of:
	/usr/local/go/src/github.com/motemen/go-colorine (from $GOROOT)
	/home/circleci/go/src/github.com/motemen/go-colorine (from $GOPATH)
blogsync/loghttp.go:7:2: cannot find package "github.com/motemen/go-loghttp" in any of:
	/usr/local/go/src/github.com/motemen/go-loghttp (from $GOROOT)
	/home/circleci/go/src/github.com/motemen/go-loghttp (from $GOPATH)
blogsync/loghttp.go:8:2: cannot find package "github.com/motemen/go-loghttp/global" in any of:
	/usr/local/go/src/github.com/motemen/go-loghttp/global (from $GOROOT)
	/home/circleci/go/src/github.com/motemen/go-loghttp/global (from $GOPATH)
blogsync/broker.go:11:2: cannot find package "github.com/motemen/go-wsse" in any of:
	/usr/local/go/src/github.com/motemen/go-wsse (from $GOROOT)
	/home/circleci/go/src/github.com/motemen/go-wsse (from $GOPATH)
blogsync/main.go:10:2: cannot find package "github.com/urfave/cli" in any of:
	/usr/local/go/src/github.com/urfave/cli (from $GOROOT)
	/home/circleci/go/src/github.com/urfave/cli (from $GOPATH)
blogsync/config.go:7:2: cannot find package "gopkg.in/yaml.v2" in any of:
	/usr/local/go/src/gopkg.in/yaml.v2 (from $GOROOT)
	/home/circleci/go/src/gopkg.in/yaml.v2 (from $GOPATH)

Exited with code exit status 1
```

【現象】パッケージ不足でblogsyncをバイナリ化できない
【再現率】`$ go get`で導入しなかった場合ほぼ100%
【原因】main.goでimportしているパッケージが実行環境に存在しない
【解決策】足りないと言われているパッケージを個別にインストールする

`$ go get blogsync`ができればこうはならないはずですが……それぞれについて`$go get`するように`config.yml`を修正するべく、[2-3.](#2-3-死闘)に進みます。

## 9-4. CircleCIのconfigファイルが読み込めない

:speaking_head:「失敗です」

```bash
$ #!/bin/sh -eo pipefail
# No configuration was found in your project. Please refer to https://circleci.com/docs/2.0/ to get started with your configuration.
# 
# -------
# Warning: This configuration was auto-generated to show you the message above.
# Don't rerun this job. Rerunning will have no effect.
false

Exited with code exit status 1
```

【現象】CircleCIがconfigファイルを検知できずテストができない
【再現率】100%
【原因】configファイルの拡張子が異なる
【解決策】config.yamlではなくconfig **.yml** を作る

[Does not take `.circleci/config.yaml` file for node project - CircleCI](https://discuss.circleci.com/t/does-not-take-circleci-config-yaml-file-for-node-project/21902/7)

ドキュメント通りに用意しましょう。
確認したら、[2-2.](#2-2-調整)に進みます。

## 9-5. blogsyncのインストールに失敗する(cli-CircleCI)

:speaking_head:「失敗です」
:hugging:「んに"ゃあああああああ！ お手上げです ＿(：3 」∠ )＿ 」

```bash
$ #!/bin/bash -eo pipefail
cd ${GOPATH}/src/github.com/motemen/blogsync && go build -o blogsync
# github.com/motemen/blogsync
./main.go:17:15: cannot use []cli.Command literal (type []cli.Command) as type []*cli.Command in assignment
./main.go:157:15: cannot use cli.BoolFlag literal (type cli.BoolFlag) as type cli.Flag in array or slice literal:
	cli.BoolFlag does not implement cli.Flag (Apply method has pointer receiver)
./main.go:158:17: cannot use cli.StringFlag literal (type cli.StringFlag) as type cli.Flag in array or slice literal:
	cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver)
./main.go:159:17: cannot use cli.StringFlag literal (type cli.StringFlag) as type cli.Flag in array or slice literal:
	cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver)

Exited with code exit status 2
```

【現象】コンパイルに失敗してblogsyncをバイナリ化できない
【再現率】100%（2019/12/21現在。おそらく2019年10月以前なら0%）
【原因】main.goでimportしているCLI用パッケージ（urfave/cli）にバージョン互換性がない（v1→v2で破壊的な変化をしている）
【解決策】ワカリマセン

もうblogsyncにissue記し奉るしか手がないと思われます……。
四方八方試行錯誤も堂々巡りで万策尽きて、[終幕](#z-おお-よ)に進みます。


# Z. おお ○○○○よ
:speaking_head:「**しんでしまうとは　なさけない…。**」
:head_bandage:「GoとCircleCIの知識を経験点として、次のキャンペーンに進んでいいですか……」