くりにっき

フルスタックキュアエンジニアです

circleci-bundle-update-prをCircleCIのorbにした

circleci-bundle-update-prとは

CircleCIでbundle updateしてPRを作るgemです。(id:masutaka26 氏製作)

github.com

モチベーション

コアの処理はgemになっていてCircleCIから呼ぶ部分は実質ワンライナーなのでorb化不要な気もしましたが、あるとそれなりに便利そうなので作ってみました

使い方

だいたいこれで動きます。

version: 2.1

orbs:
  ruby-orbs: sue445/ruby-orbs@1.3.0

workflows:
  version: 2

  nightly:
    triggers:
      - schedule:
          cron: "00 10 * * 5"
          filters:
            branches:
              only: master

    jobs:
      - ruby-orbs/bundle-update-pr:
          image: "circleci/ruby:2.5.3"
          pre-bundle-update-pr:
            - run:
                name: "Set timezone to Asia/Tokyo"
                command: "sudo cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime"
          git_user_name: "yourname"
          git_user_email: "you@example.com"
          github_access_token: "$GITHUB_ACCESS_TOKEN"

パラメータの一覧や詳しい説明はREADMEを御覧ください。

https://github.com/sue445/circleci-ruby-orbs#bundle-update-pr

実際に動いた画面

f:id:sue445:20181118151141p:plain

合わせて読みたい

sue445.hatenablog.com

sue445.hatenablog.com

CircleCI orb Perfect Testing

sue445.hatenablog.com

で書いてた

今回作ったorbのインテグレーションテストも頑張ってるのですが、長くなるので別の機会に書こうと思います

の件です。

https://github.com/sue445/circleci-ruby-orbs/blob/1.2.0/.circleci/config.yml の解説をします

注)タイトルは半分釣りです

tl;dr;

  • 何をテストしたいかによって難易度が変わる

orbのテストについて

https://github.com/CircleCI-Public/config-preview-sdk/blob/master/docs/orbs-testing.md より抜粋

You can think of orb testing at 4 levels, in increasing levels of complexity and scope.

  1. Schema Validation - this can be done with a single CLI command and checks if the orb is well-formed YAML and conforms to the orb schema.
  2. Expansion Testing - this can be done by scripting the CircleCI CLI and tests whether the elements of the orb generate the configuration you intended when processing configuration containing those elements.
  3. Runtime Testing - this requires setting up separate tests and running them inside a CircleCI build.
  4. Integration Testing - this is likely only needed for fairly advanced orbs or orbs designed specifically as public, stable interfaces to 3rd-party services. Doing orb integration tests requires a custom build and your own external testing environments for whatever systems you are integration with.

英語は苦手なので 今回は和訳することが目的ではないので具体的にどんなテスト手法があるのかを紹介します

YAMLシンタックスチェック

実際のソース

https://github.com/sue445/circleci-ruby-orbs/blob/1.2.0/.circleci/config.yml#L7-L14

一番お手軽ですが、ymlファイルの検証だけで実際にジョブを動かさないためテストとしては不十分です

ローカルテスト

CircleCI Orbs 入門 | tsub's blog の「Orb のテスト実行」が詳しいです

実際のソース

だいたいの場合はこれでいいのですが、orb実行のために他にファイルが必要な場合( ruby-orbs/bundle-install の場合Gemfile)には不向きです

理由

インテグレーションテスト

https://github.com/CircleCI-Public/artifactory-orb ではスモークテストのためのリポジトリhttps://github.com/eddiewebb/artifactory-orb-test )を作っていました。

それを参考にruby-orbsでも https://github.com/sue445/circleci-ruby-orbs-test のようなリポジトリとCircleCIのプロジェクトを作っています

circleci-ruby-orbs(メインのリポジトリ)とcircleci-ruby-orbs-test(スモークテスト用のリポジトリ)を連携させて下図のようなインテグレーションテストを実現しています。(文字が小さくなってすいません。。。)

f:id:sue445:20181116014650p:plain

仕組み

  1. 【ローカル】トピックブランチをpush
  2. 【circleci-ruby-orbs】sue445/ruby-orbs@dev:${CIRCLE_SHA1} (CIRCLE_SHA1はビルド実行時のgitのリビジョン)でorbをリリース
  3. 【circleci-ruby-orbs】smoke_testジョブでcircleci-ruby-orbs-testのリポジトリをcloneする
  4. 【circleci-ruby-orbs】cloneしたcircleci-ruby-orbs-testで smoke_test/{CIRCLE_SHA1} のようなブランチを作る
  5. 【circleci-ruby-orbs】circleci-ruby-orbs-testが sue445/ruby-orbs@dev:${CIRCLE_SHA1} を使ってビルドするようなコミットを作る
  6. 【circleci-ruby-orbs】circleci-ruby-orbs-testにpush
  7. 【circleci-ruby-orbs-test】circleci-ruby-orbsのCircleCIから下記画像のようなコミットがpushされてくるので、circleci-ruby-ops-test側のCircleCIがビルドを実行する
    • f:id:sue445:20181112013800p:plain
  8. 【circleci-ruby-orbs】circleci-ruby-orbs-testのビルドをポーリングして、ビルドが終了するまで待つ

【おまけ】スモークテスト用のブランチを自動で削除する

circleci-ruby-orbs-testには smoke_test/xxxxx のようなブランチが大量に作られるので、それを定期的に削除するCircleCIのジョブを作ってます

今の設定だと12時間に1回、12時間以上前に作られたブランチを自動で削除するようにしています

【おまけ】orbリリース時に自動でgitのtagをpushしたい

orb-tools/increment を使えばorbの自動リリースは簡単にできるのですが、リポジトリ的にはリリースごとにtagが打たれていた方が後からバージョン間の差分が取りやすいので嬉しいことが多いです。

increment後に自動でgitのtagをpushする方法がないかissueで聞いたところ、

github.com

You could run circleci orb info to get the latest in an additional step in your flow

というレスをもらったのでそれをCIで実装したのが下記です

circleci orb info をたたいてLatestで返ってきたバージョンのtagを作ってpushしています

*1

*1:余談ですがここだけrubyを使っているのはシェルで標準出力を正規表現のparseするのが難しかったためです

Itamae v1.10.0をリリースした

リリースノート

https://github.com/itamae-kitchen/itamae/blob/master/CHANGELOG.md#v1100

今回の差分は1つだけですが、個人的には目玉機能だと思ってます。

Support only_if and not_if inside a define

github.com

v1.9.xまで

みんな大好き define *1 ですが、define の呼び出しの中では only_ifnot_if が使えなくて define の実行自体をスキップするには下記のように外部から無理やり条件を渡すしかありませんでした

define :install_and_enable_package, version: nil, only_if: nil do
  v = params[:version]
  only_if_condition = params[:only_if]
  package params[:name] do
    version v if v
    action :install
    only_if only_if_condition
  end

  service params[:name] do
    action :enable
    only_if only_if_condition
  end
end

install_and_enable_package "nginx" do
  only_if "ls /tmp/install_eginx"
end

v1.10.0以降

define の呼び出しの中で only_ifnot_if を使えるようにしたので、今までのようなハックは不要になり下記のように他のリソース同様シュッと書けるようになります。

define :install_and_enable_package, version: nil do
  v = params[:version]
  package params[:name] do
    version v if v
    action :install
  end

  service params[:name] do
    action :enable
  end
end

install_and_enable_package "nginx" do
  only_if "ls /tmp/install_eginx"
end

既存のレシピやプラグインの中でdefineを多用している場合はv1.10.0に上げることでリファクタリングできるようになると思われます。

*1:要出典

CircleCI 2.1のorbを作って最速で実アプリに投入した

tr;dr;

【前置き】先日の出来事

2.1 preview自体は結構前から出ていたのですが、先日正式リリースされました

orbとは

  • .circleci/config.yml をモジュール化する仕組み
    • 複数リポジトリで共通する処理を1箇所に集約できる
    • 自分が作ったorbを配布もできる
  • WerckerでいうところのStep Marketplace
  • GitLabCIには以前からStarter以上(有料プラン)では使えていたが、v11.4でCore(無料プラン)でも使えるようになった

【今回作ったもの】sue445/ruby-orbs

個人アプリ10個くらいCircleCIを使っているのですが、全部CircleCI 2.1にアップデートしてこのorbを導入しています

モチベーション

  • CircleCIでキャッシュをいい感じに利用しつつbundle install するにはコツがいる
    • どれくらいコツがいるのかというとCircleCIのcache key戦略だけでエントリ1つ書けるくらい
  • アプリ作る度に下記のような設定を毎回コピペするのが苦痛(10リポジトリくらいある)

.circleci/config.yml

save_bundle_cache_option: &save_bundle_cache_option
  key: v1-bundle-{{ checksum "Gemfile.lock" }}
  paths:
    - ~/app/vendor/bundle

restore_bundle_cache_option: &restore_bundle_cache_option
  keys:
    - v1-bundle-{{ checksum "Gemfile.lock" }}
    - v1-bundle

jobs:
  rspec:
    <<: *default

    steps:
      - checkout
      - restore_cache:
          <<: *restore_bundle_cache_option
      - run: ./.circleci/setup_bundle.sh
      - save_cache:
          <<: *save_bundle_cache_option

      - run: bundle exec rake test

.circleci/setup_bundle.sh

#!/bin/bash -xe

bundle install --jobs=4 --retry=3 --path vendor/bundle
bundle clean

# Resolve bundler version difference between Gemfile.lock and pre-installed in CI
gem install restore_bundled_with --no-document
restore-bundled-with

そこでこの機会に今までの知見をorbにしました

準備

organizationの設定で3rdパーティ製orbを有効にしておく必要があります。(デフォルトOFF)

f:id:sue445:20181110161451p:plain

また、リポジトリのAdvanced SettingsでEnable build processingがOffのままだと現時点だと2.1の記法が解釈できなくてvalidate errorになったので、ここもOnになってるか確認しましょう

f:id:sue445:20181110161737p:plain

使い方

.circleci/config.yml に下記のようなのを書くだけです

version: 2.1

orbs:
  # Use specific version
  # see. https://github.com/sue445/circleci-ruby-orbs/releases
  ruby-orbs: sue445/ruby-orbs@1.1.2

  # or
  # Use latest version
  # ruby-orbs: sue445/ruby-orbs@volatile

jobs:
  rspec:
    steps:
      - checkout

      - ruby-orbs/bundle-install
      # or
      - ruby-orbs/bundle-install:
          cache_key_prefix: "v1-bundle"
          bundle_jobs: 4
          bundle_retry: 3
          bundle_path: "vendor/bundle"
          bundle_clean: true
          bundle_extra_args: ""
          restore_bundled_with: true

      # Add your job (e.g. rspec, rubocop)
      - run: bundle exec rspec

補足

現時点ではGemfile.lockがコミットされてるリポジトリのみサポートしています。(具体的にはgemみたいにGemfile.lockをコミットしていないリポジトリでは使えない)

理由

  • CircleCIのキャッシュのキーにGemfile.lockのチェックサムを利用してるため
  • gemのCIでCircleCIを使ったことはあるが、色々考慮することがある&仕組が複雑になるので自分の中でベストアンサーがまだ見つかってないので作りかねてる

CircleCI 2.0から2.1に移行したPR

手持ちのアプリ10個くらい全部CircleCI 2.1対応したので、そのうちPRを1つだけ紹介します

Migrate to circleci 2.1 by sue445 · Pull Request #334 · sue445/sebastian-badge · GitHub

差分を見てもらえれば分かると思いますが、記述が大幅に減っています

ついでにHerokuにデプロイするやつもorbに寄せた

https://github.com/CircleCI-Public/circleci-orbs/tree/master/src を見てたらHerokuデプロイ用のorbもあったので導入しました

https://circleci.com/orbs/registry/orb/circleci/heroku

Before

After

補足

post-deploy

  • CircleCI 2.1だとstepを引数として受け渡しできるため、orbの処理は使いたいが特定の場所に任意の処理を差し込みたいということができる
  • 今回の場合、heroku/deploy-via-gitpre-deploypost-deploy というフックポイントを引数で用意しているので、デプロイ後に heroku run rake ar:migrate するために post-deploy を使ってる

Context

  • Herokuにdeployする時の $HEROKU_API_KEY をcontextで定義してると、同じ環境変数を各リポジトリの設定のEnvironment Variablesではなく、organizationの設定1箇所に集約できて便利
    • 2.1の新機能ではなく(たぶん)2.0時代からあった
  • Herokuのパスワード変えたり二段階認証ON/OFFするとAPI Keyも再発行されるので1箇所で集約されてると何かと便利
  • heroku/deploy-via-git もデフォルト値は環境変数$HEROKU_API_KEY から取ってきてた

f:id:sue445:20181110163549p:plain

one more thing

  • 今回作ったorbのインテグレーションテストも頑張ってるのですが、長くなるので別の機会に書こうと思います
    • どれくらい頑張ってるのかというと、orbのインテグレーションテストとその周辺技術だけで30分くらい話せるボリューム

追記:2018/11/16

書いた

sue445.hatenablog.com

pry-docにPRを投げた

pry v0.12.0で色々deprecationになって関連gemでもdeprecation warningが出るようになってたので、PRチャンスということで投げました

https://github.com/pry/pry/blob/master/CHANGELOG.md#v0120-november-5-2018

pry-doc v0.13.5

PR 2つともわたしです ^q^

Fixed deprecation warnings emitted by Pry v0.12.0

github.com

pry v0.12.0のdeprecation warning対応

Fixed MRI 2.0 regression

github.com

87のPR投げたらRuby 2.0でCIコケてたのでついでに直した

メンテナがRuby 2.0のkeywoard argsとRuby 2.1のrequired keyword argsを勘違いしていたので説明するのが大変だった・・・

https://github.com/pry/pry-doc/pull/88#discussion_r231043234

Itamae v1.9.13をリリースした

CHANGELOGに載ってる修正

https://github.com/itamae-kitchen/itamae/blob/master/CHANGELOG.md#v1913

Fixed. Can not create empty file

github.com

fileリソースで空ファイルを作れないバグの修正。

github.com

元々remote_fileやtemplateで空ファイル作れていたのですが、いつのバージョンからか作れなくなっていたのを前職時代に観測してました。

その時は execute "touch xxx"で雑に回避してたのですが、今回mitamaeと同じ修正を入れて解決しました。

CHANGELOGに載ってない修正

Fixed rspec warning

github.com

travisのweekly buildのログを見てたら

expect {
  subject.event(:name, :arg) { raise "name is failed" }
}.to raise_error

でwarning出ていたのを直しました。

Fixed. test is failing after run test

github.com

これ英語が怪しいんだけど、要はローカルで開発してる時に同じコンテナに対して同じレシピを2回適用するとItamaeがコケるという現象があって直しました。

-execute "mkdir /tmp/link-force-no-dereference1"
+execute "mkdir -p /tmp/link-force-no-dereference1"
 link "link-force-no-dereference" do
   cwd "/tmp"
   to "link-force-no-dereference1"
   force true
 end

Pixelaで自分のツイート数を草化した

こんな感じです。

pixe.la

リポジトリ

github.com

仕組み

Twilogスクレイピングして、CircleCIのcronの仕組を使って定期的(当日分は1時間おき、前日分は1日1回)にツイート数をグラフを更新しています。

スクリプトRubyで書いていますが、定期実行部分に関してはCircleCIとDockerイメージだけで完結しているので実行環境にRubyをインストールする必要はありません。

追記:2018/11/26

1時間おきは多すぎた感があったので今は6時間おき(1日4回)にしてます

Twilogを使ってる理由

  • 特定の日時のツイート数だけを取得するだけならTwitter APIよりTwilogからとってくる方が早いし楽
    • Search APIでも日付を絞り込むことはできるのだが、自分の経験上たまに検索結果が取れなくなることがあるのであまり信用してない *1
    • 特定のユーザのツイートを取得するならuser timeline APIを使うのが確実なのだが直近3200件分しかとれない。
      • 当日分と前日分くらいなら問題ないんだが、それでもuser timetimeで特定の日付を絞ってツイートを取得するのは若干面倒
    • 過去分を一括投入しようとするとRateLimitに引っかかってしまう
    • Twilogであれば https://twilog.org/sue445/stats に全期間(自分が登録した以降全部)のツイート数が入ってるので、何回もAPIを叩くよりもhtmlを1枚スクレイピングする方が圧倒的に楽と判断
  • Twitter APIだと自分でアプリ(APIをたたくためのアクセストークン)を作る必要があってお手軽感がない
  • 過去分投入だけなら公式のツイートダウンロードでもいいんだけど、OSSとして公開しようとすると英語で説明を書くのが面倒だしスクリプト化しづらい

使い方

基本的には https://github.com/sue445/tweet_pixels/blob/master/README.md に書いてる通りにコマンドを実行するだけ。

最初のPixela登録とグラフ作成だけローカルで実行する必要があります。

Rubyでやりたい場合は https://github.com/sue445/tweet_pixels をforkしてclone後に下記のようなコマンドを実行してください。

cp .env.example .env
vi .env
bundle install --path=vendor/bundle

./bin/console
# register
@client.create_user(agree_terms_of_service: true, not_minor: true)

# create graph
@graph.create(name: "Daily tweets", unit: "Tweets", type: "int", color: "sora")

他言語の場合は同じようなことをよしなにやってください。

直近1年分のツイートを一括投入する場合は bundle exec rake update_multi を実行

グラフ作成後はCircleCIのEnvironment Variablesに

  • TWITTER_ID
  • PIXELA_USERNAME
  • PIXELA_TOKEN
  • PIXELA_GRAPH_ID

を登録してください。

f:id:sue445:20181021110424p:plain

Tips

CircleCIでボットを運用する時のノウハウとして、Chat Notificationsで普段使いのチャット(自分の場合Slack)を登録しておくとエラー時にすぐに気付けるので便利です。

f:id:sue445:20181021110928p:plain

1時間に1回にSlackに通知がくると逆にウザいので、自分はFixed/Failed Only(ビルドが失敗から成功に戻った時と、ビルドが失敗した時のみ通知)をよく使っています。

所感

今回のに限ったことじゃないんだけど、CircleCIの設定ファイルに実行環境(利用するDockerイメージ)やcronの設定も含めることができるので、GitHubリポジトリでボットの自動実行環境もセットで配布できるのすっごい便利だ。。。

*1: 過去に実際、一時期自分のツイートだけがSearch APIで取れなくなる事象がありました https://twitter.com/sue445/status/897968605567926272