GitHub Actionsでterraform planするworking directoryを動的に抽出する

システム開発部Misocaチームエンジニアの mizukmb です。

TerraformのCIをGitHub Actionsで実装する際に工夫した時の話を紹介します。

GitHub ActionsでCIしたいけどworkflowをどうやって書けばいいの?

TerraformコードをGitHubレポジトリで管理する場合、1 GitHubレポジトリ = 1 Terraform working directory よりも 1 GitHubレポジトリ = 複数Terraform working directory といったmonorepo構成になる事が多いと思います。

例えば以下のようなproduction環境とstaging環境それぞれのworking directoryとそれらから利用されるterraform moduleを1つのGitHubレポジトリで管理するといったケースです。

❯ tree terraform-repo                         
terraform-repo
├── modules
│   ├── ecs
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   ├── iam
│   └── vpc
├── production
│   ├── app
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   └── vpc
└── staging
    ├── review_app
    ├── staging_app
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    └── vpc

11 directories, 9 files

こうしたmonorepo構成で terraform plan 等の実行を想定したCIを構築したい場合に困りがちなのは どのworking directoryが対象であるか の判別が難しいという事です。

上の例の場合だと、 production/app/main.tf を編集したGitHub Pull Request (以降、PR) では production/app だけ terraform plan できれば十分ですし、 production/vpcstaging/vpc を編集したPRでは両方で terraform plan してほしいです。

さらに modules/ 以下のファイルを編集した場合は、 そのモジュールを利用しているworking directoryが対象になり 、単なるファイル変更があったディレクトリで terraform plan を実行すれば良いという話ではなくなってきます。

こうした要件を満たしたGitHub Actions worflowをどのように作成すればよいかを次の章で説明します。

terraform plan対象のworking directoryを抽出する

tj-actions/changed-files でPRの変更ファイル一覧を取得し、k1LoW/github-script-rubyを使ってファイルから terraform plan 対象のworking directoryを抽出するRubyスクリプトを実行し、outputsとして後続のjobに渡すといった解決策をとりました。

working directoryの抽出jobは actions/github-script でも同じ事ができますが、開発メンバーが普段からRuby on Railsを使った開発を行っているという背景から k1LoW/github-script-ruby を使う事にしました。

jobs以下はこのように書いてます。

jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      targets: ${{ steps.output-targets.outputs.targets }}
      plan_targets: ${{ steps.output-targets.outputs.plan_targets }}
    steps:
      - uses: actions/checkout@v2
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v18.6
      - name: Output target dirs
        id: output-targets
        uses: k1LoW/github-script-ruby@v2
        with:
          script: |
            # steps.output-targets.outputs.targets
            #   PRの変更したファイルから `valid_dirs` に含まれるファイルのみを抽出し、outputs.targetsを作成する
            # steps.output-targets.outputs.plan_targets
            #   outputs.targesからterraform plan対象のディレクトリを抽出し、outputs.plan_targetsを作成する
            require 'json'
            valid_dirs = [
              'production/app',
              'production/vpc',
              'staging/staging_app',
              'staging/review_app',
              'staging/vpc',
              'modules/ecs',
              'modules/iam',
              'modules/vpc
            ]
            files = '${{ steps.changed-files.outputs.all_changed_files }}'.split
            targets = valid_dirs.select { |v| files.select { |d| d.match?(v) }.size > 0 }
            plan_targets = targets.map do |t|
              # modules以下を変更した場合は参照してるworking directoryをplan_targetsの対象とする
              case t
              when 'modules/ecs', 'modules/iam'
                ['production/app', 'staging/staging_app', 'staging/review_app']
              when 'modules/vpc'
                ['production/vpc', 'staging/vpc']
              else
                t
              end
            end.flatten.uniq
            puts "Target dirs: #{targets}"
            puts "Plan target dirs: #{plan_targets}"
            core.set_output('targets', targets.to_json)
            core.set_output('plan_targets', plan_targets.to_json)

  terraform_fmt:
    runs-on: ubuntu-latest
    needs: setup
    strategy:
      matrix:
        target: ${{ fromJSON(needs.setup.outputs.targets) }}
    steps:
    - uses: actions/checkout@v2
    - name: Setup terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: ${{ env.TERRAFORM_VERSION }}
    - run: terraform fmt -recursive -diff -check ${{ matrix.target }}

  terraform_plan:
    runs-on: ubuntu-latest
    needs: setup
    defaults:
      run:
        working-directory: ${{ matrix.target }}
    strategy:
      matrix:
        target: ${{ fromJSON(needs.setup.outputs.plan_targets) }}
    steps:
    - uses: actions/checkout@v2
    - uses: aws-actions/configure-aws-credentials@master
      with:
        role-to-assume: ${{ env.AWS_ROLE_FOR_PLAN_ARN }}
        aws-region: ap-northeast-1
    - uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: ${{ env.TERRAFORM_VERSION }}
    - name: Setup github-comment, tfcmt
      run: |
           curl -sL -o github-comment.tar.gz https://github.com/suzuki-shunsuke/github-comment/releases/download/v${{ env.GITHUB_COMMENT_VERSION }}/github-comment_${{ env.GITHUB_COMMENT_VERSION }}_linux_amd64.tar.gz
           sudo tar -C /usr/bin -zxvf github-comment.tar.gz
           curl -sL -o tfcmt.tar.gz https://github.com/suzuki-shunsuke/tfcmt/releases/download/v${{ env.TFCMT_VERSION }}/tfcmt_linux_amd64.tar.gz
           sudo tar -C /usr/bin -zxvf tfcmt.tar.gz
    - name: Run terraform plan
      run: |
           terraform init
           tfcmt plan -- terraform plan -no-color
           github-comment hide
      continue-on-error: true
      env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs.setup

このjobの目的は、 terraform planterraform fmt といった本来CIでやりたい事の前処理として、対象working directoryを抽出しoutputsを設定、そして後続のjobに渡す事です。outputsには targetsplan_targets の2つを設定します。それぞれの役割は以下の通りです。

  • targets
    • modules/ 含むworking directory一覧
    • fmtやtflintで使う事を想定
  • plan_targets
    • modules/ の変更があった場合、利用しているworking directoryのみを抽出
    • なので modules/ はこちらに含まれない
    • planやapplyで使う事を想定

stepsですが、まず tj-actions/changed-files でPRの変更ファイルの一覧を取得します。outputsは勝手に設定されるので uses で指定するだけで終わります。READMEから使用可能なoutputsを確認できます。

k1LoW/github-script-ruby ではoutputsの targetsplan_targets に渡す値を抽出するRubyスクリプトが書かれています。modulesを利用するworking directoryの動的な抽出が難しそうだったのでcaseを使った分岐を入れています。monorepo内にあるworking directoryの数が多いとしんどくなるかもしれません。

まとめ

GitHub ActionsでPRの変更ファイルから terraform plan の対象となるTerraform working directoryの動的な抽出を行うworkflowの書き方について書きました。これによってplan結果をPR毎に簡単に確認できるようになり、開発体験の向上を感じています。

参考文献

workflowの作成にあたってsuzuki-shunsuke/tfactionの実装や設計思想を参考にしており、強く影響を受けています。説明を省略していましたが、 terraform_fmtterraform_planstrategy.matrix 使うといったアイデアはまさにこちらを参考にしたものです。working_directoryの動的抽出の方法もtfactionに刺激された結果として実装アイデアを思いつく事ができました :)

お知らせ

弥生では一緒に働く仲間を募集しています! herp.careers

もくテク「エンジニア就活生必見! 新卒エンジニアってどうなの?」を開催しました

こんにちは、弥生の相澤です。
毎月恒例のもくテク開催報告です。

イベント概要

今回は就活生や学生に向けたイベントを開催しました。

今回のテーマは『エンジニア就活生必見! 新卒エンジニアってどうなの?』 今回のもくテクは、「弥生が気になっている」という就活生のための弥生開発本部紹介企画! このイベントでは弥生に新卒入社した2年目~5年目のメンバーが登壇し、研修や配属先、今任されている仕事について包み隠さずお話しさせていただきます! 採用選考には一切関係のないカジュアルな会なのでぜひ気軽に参加してください😼

前半にLTで弊社の新卒の経験をお伝えし、後半に参加者の方々から送っていただいた質問をパネルディスカッションで回答するという構成で進めました。

LTの内容

『開発本部紹介』 by 中山 健太

開発本部のリーダーが本部についてをご紹介しました。
組織の取り組みから、キャリアパス、成長支援についてなどを熱い思いとちょっとの笑いを交えてお話しました。
例えば、開発本部のキャリアパス例としてエンジニアからテックリード、プロジェクトマネージャー、品質リーダーなど様々なキャリアを選択できます。キャリアアップを支援するため、新卒の社員には入社直後プログラミング研修があったり、新卒・中途問わず入社後定期的に技術研修やマネジメント研修などを受ける機会があります。
他にも1on1という定期的に上司と1対1で話す時間や、本部全大会という本部員全員が集まる会で各チーム/人の取り組みをLTとして発表する時間があったりと、様々な取り組みによってエンジニアの活躍や成長が支えられています。

『研修って大変なの?新卒技術研修Q&A』 by 田邊 慎史

新卒2年目の社員が開発未経験だった過去の自分に向けて、研修を受けた経験や受ける際のポイントについてQ&A形式でお話しました。
弊社では最初から学ぶ前提で研修などの計画が組まれているため入社後からのスタートでも大丈夫なこと、講師や先輩など周囲のサポートを活かせることなどを発表しました。

『知識の無い新卒が新サービスを作るチームに参画できた物語』 by 増田

新卒3年目の社員が新サービスチームに参画したいという希望を伝えてからの経験についてお話しました。
希望通りのチームに参画してからは「複数人で実装したい」と仕事の進め方を提案したり、StorybookCodeceptJSなど弊社でまだ使っていなかった新しい技術を調査したりして新しいことに挑戦されています。

『開発未経験でもAPIを実装できる⁉️~弥生スポットライト賞を受賞するまでの取り組み~』 by 松坂 莉奈

入社直後は開発未経だった験新卒4年目の社員が、それから新卒2年目で社内賞を受賞するまでに至った経験についてお話ししました。
10個以上のAPIをGo言語で作成するために、
①まずは既にあるものを理解しようとする
②そこで出た疑問はすべて解消する
③やるべきことを明確にする
④自力で調べる力を身に着ける
というステップを踏んで腕を磨いていったことを発表しました。

『「上司」って激ムズ…!新卒4年目サブリーダーの葛藤と感謝』 by 野川

新卒5年目の社員が人事ライン(※)というチームのサブリーダーになった経験と、そこで気づいた上司として大切なポイントやチームメンバーの声をお話しました。
 ※開発本部では物を作るファンクションというチームと、勤怠管理・評価・メンタルケアなどの人を見る人事ラインというチームが存在します。今回は人事ラインチームのサブリーダーについての内容になります。
・完璧じゃなくていい
・信頼関係が何より大事
・部下の話を"聴く"
・Iメッセージで伝える
ということを意識してメンバーをサポートし、チームメンバーから「本音で話すことができ課題の解決に近づけた」「近い目標として自分のキャリアを考える際にも助けられている」という声をいただいています。

パネルディスカッションの内容

司会は開発本部紹介をしたリーダーが、パネラーは新卒2~5年目のLT登壇者と運営司会者が登壇しました。
ありがたいことに当日前や開催中も参加者の方からご質問をいただきました。
ここで本番で回答できなかった質問をこちらの記事で回答させていただこうと思います。

質問内容

エンジニア就活の時期感(インターン、ES、内々定)、持っておいた方がいい能力/資格/実績、就活始めるまでどういう心持で生活すればいいか

回答

エンジニア就活の時期感(インターン、ES、内々定)について

FY22については11月頃からエントリーを受けつけ、内々定は年明けくらいから出しています。
また、現状インターンは行っておりません。

持っておいた方がいい能力/資格/実績

能力、資格は特にありません。
実績は下記のとおりです。(※必須ではなく、あると歓迎、という意味合いです)
・自ら目標を立て、やり切った経験
・チームを巻き込み、成果をあげた経験
・社会に対して何かしら課題意識を持っており、貢献したいと思っている

就活始めるまでどういう心持で生活すればいいか

私個人の考えですが、勉強やサークル活動、バイトなど興味があることにどんどん打ち込めば良いと思います。
また、そうやって打ち込んできた事柄を周りの人に話したり何かにまとめたりして自分の言葉でたくさん伝えてみると、就活での引き出しが増えて、結果的に自分に合った会社が見つかると思います。

次回のご案内

次回は『『100アカウントを見据えた』AWSマルチアカウント運用』というテーマで開催します。

今回のもくテクでは、弥生の実業務でも利用しているAWSマルチアカウントの運用方法をご紹介します。 1つのAWSアカウントに複数のサービスを入れてみたが「これで大丈夫かな?」「サービス増えたら破綻するのでは・・・?」と心配しているエンジニア向けに、シングルアカウントで発生する問題とマルチアカウント化することによる解決方法をお伝えする予定です。 マルチアカウント化にあたり個社ごとに検討しなければいけない課題についてもお伝えしますので、ぜひご参加ください!

AWSのアカウント運用についてがっつりお話しさせていただきます。
ご興味のある方はぜひご参加ください。

mokuteku.connpass.com

一緒に働く仲間を募集しています

www.yayoi-kk.co.jp

herp.careers

もくテク「freee×弥生!ユーザーの喜びの声を愛でる会」を開催しました

f:id:issi-y:20220328130855j:plain

こんにちは、弥生のいっしーです。

すっかり春ですね。先週末は近所の公園で桜を愛でてきました。
リモートワークはついつい運動不足になりがちなので、季節を感じるイベントは心も身体もリフレッシュできておススメです。

実は3月は、お客さまの喜びの声を愛でる機会もありました。
こちらもとっっっても良かったので、当日の様子をご紹介します。

3月のもくテクは「freee×弥生!ユーザーの喜びの声を愛でる会」

今回はなんと、普段は競合として見られがちなfreee株式会社さんとのコラボイベントでした!

今回のイベントでは、freeeと弥生それぞれから会計ソフトの開発に携わるエンジニアが登壇し、お互いの会社や製品・サービスの開発についてトークします。
トークの中でユーザーの方からの「喜びの声」をご紹介し、会計をはじめとするバックオフィス業務向けのソフト開発の楽しさを全力でお伝えする予定です!
会社の壁を越えて開発の楽しさ、やりがいを分かち合うこの貴重な機会を、ぜひお見逃しなく!!

YouTube Liveでの限定生放送*1でしたが、多くの方にご視聴いただくことができました。

お互いを探り合う自己紹介

ゲストはfreeeの中部オフィスで開発本部長を務めていらっしゃるmoai(山崎)さん。弥生からはCTOの佐々木と弥生会計オンラインのテクニカルリーダーを務めている狩野が登壇しました。自己紹介の結果、狩野が実は転職活動時にfreeeさんも検討していたことや、moaiさんと佐々木には両社のカジュアル面談で会うことができることが判明。また、両社ともコロナ禍で一気にリモートワークが進んだ話を和気あいあいとしていました。

f:id:issi-y:20220331115306p:plain
画面左上から時計回りにファシリテーターの佐々木(弥生)、moaiさん(freee)、狩野(弥生)

似ている部分、違う部分が見えてきた会社紹介

場も温まってきたところで両社の会社紹介です。
freeeさんも弥生も、スモールビジネスのバックオフィスを支えるプロダクトをリリースしているだけあり、ミッションは非常に似ていて共感できるものでした。「我々のお客さまはどんな人たちでどんな課題を抱えているのか?」という話題についても、お互いに頷くばかり。

両社ともスモールビジネスが世の中に良い影響を与える存在だと考えている点は共通していましたが、弥生は「事業コンシェルジュとして、事業のあらゆるフェーズで発生する困りごとを一緒に解決する」、freeeさんは「統合型経営プラットフォームを提供することで、だれもが自由に自立的に経営できる(経営のハードルを下げる)」というアプローチの違いを感じました。

f:id:issi-y:20220331115237p:plainf:id:issi-y:20220331115322p:plain
弥生の会社紹介

f:id:issi-y:20220331115335p:plainf:id:issi-y:20220331115346p:plain
freeeさんの会社紹介

ユーザーの喜びの声を愛でる

そして、いよいよ今回のメインパートです!
ここからは、お客さまからの喜びの声の多かった機能を開発したメンバーも参加してお話しいただきました。freeeさんからはfreee会計のマネージャーをしているnoripee(加藤)さん、弥生からは弥生会計デスクトップのテクニカルリーダーの寺﨑(柴犬)*2が登壇しました。

それぞれのプロダクトの機能やお客さまの喜びの声を聞きながら、

  • タグとか数年経つと管理が大変になりますよね
  • あー、これはかゆいところに手が届いていいですね
  • この機能、うちもやりたいとは思ってるんですけどできてないんですよ
  • リリースした機能が喜んでもらえると嬉しいですよね
  • お客さまの声はちょくちょくチェックしてます

といった開発者らしい意見交換をされていて、とても楽しそうでした。

freee

freeeさんからは「ワークフローの共有機能」「タグの非表示機能」「試算表・月次推移での検索条件の保存機能」の3つをご紹介いただきました。

f:id:issi-y:20220331115403p:plain
タグの非表示機能

■ユーザーの喜びの声(一部抜粋)

  • ワークフローに共有機能が実装されてて地味にいいやん、って思った
  • 待ってました!!最高です!!
  • 検索条件の保存機能がイイね

弥生

弥生からは「前期データの参照機能」を紹介しました。

f:id:issi-y:20220331115413p:plain
前期データの参照機能

■ユーザーの喜びの声(一部抜粋)

  • 前年度データの参照が楽になった。やるじゃん
  • かなり効率化です
  • 神実装

感想タイム

イベントの最後に、登壇者から今回の感想をいただきました。

  • 普段の業務だとどうしても競合他社という目線で見てしまうが、今回はその目線抜きのポジティブな感じで、お互いの良いところやお客さまの反応について話せたのがよかった
  • 業界を盛り上げていく良きライバルとして、切磋琢磨しながらこれからも一緒に頑張っていけたらと思った
  • ユーザーさんの利用率や声を知れるのはやはり楽しいし嬉しい
  • お客さまにいいものだと思ってもらうだけでなく、自分たち自身もこれはいいものなんだと言える機能開発をしていきたい

f:id:issi-y:20220331115425p:plain
みなさん、いい笑顔!

freeeさん、今回は本当にありがとうございました!
また機会があればぜひコラボさせてください!

次回のご案内

さて、次回は「エンジニア就活生必見! 新卒エンジニアってどうなの?」というテーマで開催します。
就活シーズンに入り、進路に悩まれている方も多いのではないでしょうか?
このイベントでは弥生に新卒入社した2年目~5年目のメンバーが登壇し、研修や配属先、今任されている仕事について包み隠さずお話しします! 選考とは関係ありませんので、弥生の新卒エンジニアの生の声を聞きに、ぜひお気軽にご参加ください。

mokuteku.connpass.com

一緒に働く仲間を募集しています

弥生ではスモールビジネスを一緒に盛り上げていってくれる仲間を募集しています! herp.careers

*1:今回の配信は公開する予定はありません。

*2:「あ、どうも。こんな姿で失礼します」と登場した柴犬に、会場が笑いに包まれたのはここだけの話。

もくテク「弥生って、実は機械学習もやってるんです!」を開催しました!

f:id:yayoi_ntake:20220222194038j:plain

こんにちは。弥生でエンジニアをしている、たけです。

2022年2月17日(木)に毎月恒例のもくテクを開催しました。
このブログではすっかりお馴染みになってきたもくテクですが、あらためてご説明すると以下のような楽しい技術勉強会です!

弥生株式会社が運営する勉強会です。
Azure, AWS, デスクトップアプリ, UXなどなど 木曜日の夜に、みんなで楽しくIT技術について語り合いませんか?

それでは、2月のもくテクの開催報告をしたいと思います!
特別なお知らせもありますので、ぜひ最後まで読んでいただけると嬉しいです。

2月のテーマは「弥生って、実は機械学習もやってるんです!」

イベントページはこちら(※開催は終了) mokuteku.connpass.com

実は、そうなんです!
弥生が提供するサービスには機械学習を用いた機能があり、その機能は自社で研究開発を行っています。

2月のイベントでは、機械学習を用いた機能の仕組みや、研究開発、運用の方法など裏側をたっぷりご紹介しました。

セッションの内容

1. 弥生の機械学習を使っているサービスの紹介! by 白井

最初の発表は白井さん。弥生の機械学習を使っているサービス、「YAYOI SMART CONNECT」をご紹介しました。
「YAYOI SMART CONNECT」はあらゆる取引を会計製品に取り込むサービスです。
取引データを仕訳データ化する際に使っている推論エンジンに、機械学習が用いられています。
推論エンジン・APIの仕組みからチームの体制まで、今回のイベントの他の発表への理解が深まる内容の発表でした。

2. 弥生の研究開発いいところ by 土田

続いて、土田さんから推論エンジンの研究開発はどのように行っているのかという観点で発表しました。
これまでの研究開発の成果として、多段階のルールと推論を組み合わせた推論ロジックの実現、ベイズ推定からニューラルネットワークへの移行をご紹介しました。
研究開発のフロー図もお見せし、「弥生の研究開発いいところ」=「研究開発が設計、開発、リリース、運用・保守、次の研究開発に結びついていること」をお伝えしました。

3. 精度だけじゃない!推論システム運用のための取り組み by 鍋谷

最後は新卒3年目の鍋谷さんの発表です。
弥生で推論チームに配属されてから機械学習について学んだ鍋谷さん。
①基礎知識の習得、②実装の仕組みの理解、③実務で使用する分野の詳細な理解という3つの「学びのステップ」を設けて勉強してきたことをお話ししました。
続いて、実用化に至るAIプロジェクトが少ないこと、弥生で実用化できていることへの考察を発表し、運用コストを上回る提供価値の重要性をお伝えしました。

2月のもくテクの開発報告は以上です!
私も弥生で製品の開発を行っていますが、推論エンジンの研究開発の難しさやおもしろさが伝わってきて、とてもよい刺激を受けました。

お知らせ(次回予告)

次回2022年3月17日(木)のもくテクは、初のコラボレーション企画!
なんとお相手は「会計」という領域でしのぎを削っている、freee株式会社さんです!

テーマは「freee×弥生!ユーザーの喜びの声を愛でる会」。
イベントではそれぞれの製品・サービスのユーザーの方からの「喜びの声」をご紹介し、バックオフィス業務向けのソフト開発の楽しさをお伝えします!

お申し込みは以下のconnpassのページから可能です。
mokuteku.connpass.com

どんなイベントになるのか、とても楽しみです。
スペシャルなイベントになると思いますので、ぜひみなさまもお見逃しなく!!

お知らせ

弥生では一緒に働く仲間を募集しています! herp.careers

Misoca の PDFテスト

こんにちは、弥生の日高 @hidakatsuya です。普段は クラウド見積・納品・請求書サービス「Misoca」 の開発に携わっています。

Misoca には、作成した請求書などの帳票を PDF としてダウンロードするだけでなく、PDF の内容を印刷したり、郵送したり、FAX として送信するなど、PDF が関連する機能が多くあります。そして、毎日非常に多くの PDF が Misoca 上で生成されています。

今回は、そんな Misoca の PDF をどのようにテストしているのかについてまとめたいと思います。

Misoca の PDF

本題に入る前に、Misoca で扱っている PDF についてもう少しだけ説明しておきます。

  • 請求書・納品書・見積書・注文書・注文請書・検収書・領収書の7種類の PDF がある
  • 請求書・納品書・見積書は複数のテンプレートを提供している
  • 執筆時点で、請求書には14種類、見積書・納品書にはそれぞれ2種類のテンプレートがある

また、2020年以降での PDF に関するリリース(ある程度の規模の機能追加や変更)回数を集計したところ、24回のリリースを実施していました。月に平均2回程度、PDF に関するリリースをしていたということになります。

Misoca の PDFテスト

これだけの種類の PDF をこの頻度で安全にリリースするためには、自動テストの仕組みは不可欠です。Misoca でも、CI として PDF 生成の結果を自動テストする環境を整えています。

  • テストケースごとに生成した「実際の PDF」と「期待するPDF」と比較し、一致しない場合は失敗として差分結果も出力する
  • 帳票の種別ごと、テンプレートごとに 8~25 のテストケースがあり、合計で 482 ケースある
  • それなりに重いので、リリーステスト*1pdf_spec_*ブランチの push 時にテストを実行する

これら一連のテストを Misoca チームでは「PDF spec」と呼称しています。

PDF spec

PDF spec は、他のテスト同様に RSpec で動作します。テストコードは spec/pdf/ に配置し、あくまで Misoca の spec の一部に過ぎません。PDFの比較には、diff-pdf を使い、PDF ファイルを直接比較することでテストします。

構成と実装

請求書のスタンダードテンプレートの spec を例に詳細を説明します。

Rails.root/
 ├── app/
 :
 ├── spec/
      ├── pdf/
      :    ├── invoice/STANDARD/
           :    ├── expected
                │    ├── general_usage.pdf
                │    ├── max_input.pdf
                │    :
                └── pdf_spec.rb

pdf_spec.rb がテスト本体で、expected/ 配下の general_usage.pdf などがテストケースの「期待するPDF」です。

pdf_spec.rb は、実際とは少し異なりますが、概ね次のような実装になっています。

RSpec.describe Invoice, type: :pdf do
  include_context 'pdf spec'

  it_behaves_like 'pdf spec: general usage'
  it_behaves_like 'pdf spec: max input'
  ...
end

「期待するPDF」との比較テストは include_context 'pdf spec' の中で行なわれます。

it { expect(subject).to match_pdf(expected_pdf_path, output_diff: diff_pdf_path) }

match_pdf で PDF の比較が行われ、一致しない場合はテストは失敗します。

この時、後述のオプションを指定すると、diff_pdf_path で指定したパスに差分のPDFが出力されます。差分は、次のように変更箇所がハイライトされた普通の PDF ファイルとして出力されます。これは diff-pdf の機能です。

f:id:hidakatsuya:20220222115124p:plain
領収書PDFのタイトルのフォントサイズを大きくした場合の差分PDF

なお、この match_pdf Matcher は、筆者作の pdf_matcher-testing gem で定義されています。*2

PDF spec の機能と bin/pdf_spec コマンド

前述の通り、PDF spec は RSpec の example の一部に過ぎないため、bin/rspec spec/pdf/invoice/STANDARD で実行することができますが、手元での開発時は専用のコマンド bin/pdf_spec を使って実行します。

以下は bin/pdf_spec の実際のコマンドヘルプです。

$ bin/pdf_spec
Usage: pdf_spec [OPTIONS] specpath

OPTIONS:
  --verbose          テスト結果の差分PDFと結果PDFを tmp/pdf_spec/ に出力する
  --update_expected  期待PDF(expected.pdf)を結果PDFで更新する。差分PDFも出力する

  詳細は spec/support/shared_context/pdf/pdf_spec_shared_context.rb のコメントを参照
  その他、rspecコマンドに渡す任意のオプションを指定できます

specpath:
  spec/pdf/ 配下の spec ファイルを指定します

Examples:
  $ bin/pdf_spec --verbose spec/pdf/invoice/STANDARD
  $ bin/pdf_spec --update_expected -fd -e "min input" spec/pdf/invoice/STANDARD ...

上記の通り、このコマンドは、単に PDF spec を簡単に実行するだけではなく、PDF の変更を行う際の PDF spec に伴う開発フローを助ける役割も担っています。

PDF に差分が生じていないかを確認する

まず、単に spec を実行すると、PDF を比較し差分が生じていないか(壊していないか)を確認できます。

$ bin/pdf_spec spec/pdf/invoice/

PDF の結果と差分が意図したものか確認する

そして、--verbose オプションを指定することで、テストケースの実際の PDF と期待する PDF との差分(あれば)を tmp/pdf_spec/ に出力して、PDF への変更内容が期待通りかを確認することができます。

$ bin/pdf_spec --verbose spec/pdf/invoice/

期待する PDF の内容を更新する

最後に、意図通りの変更ができていることを確認したら、--update_expected オプションを指定し、期待するPDFを変更後の内容で更新することができます。更新された「期待するPDF」を push して一連の実装は完了となります。

$ bin/pdf_spec --update_expected spec/pdf/invoice/

なお、このコマンドの実装は非常にシンプルなものとなっています。以下は bin/pdf_spec の一部です。

for arg in "$@"; do
  case $arg in
    "--verbose"|"--update_expected")
    mode=${arg#--}
    ;;

    *)
    args+=("$arg")
    ;;
  esac
done

if [[ ${#args[@]} -eq 0 ]]; then
  exit_with_usage
fi

PDF_SPEC=$mode bin/rspec "${args[@]}"

実は、 verboseupdate_expected などの機能は、spec 側で PDF_SPEC 環境変数の値を読み取って実現しています。そのため、bin/pdf_spec は、--verbose などのオプションを解釈して、PDF_SPEC 環境変数に適切な値をセットし、bin/rspec を実行しているだけです。

これまでの改善・工夫

最後にこれまで行なってきた改善や工夫をいくつか紹介します。

PDF spec のテストケースが多過ぎてテストが遅く、テストケースのメンテも大変

当初、請求書だけでテストケースは 2000 を超えていました。網羅性が高く安心感はありますが、通常のビルドで実行するには、あまりにも実行時間もリソースも占有しすぎていました。

これは、シンプルに「テストケースを減らす」ことで改善した形ですが、まず「PDF spec の責務」を定義することから始めました。

  • 関心のあるもの
    • PDFの表示が意図したものか
    • 表示がくずれていないか、表示されるべきところに正しく表示されているか
  • 関心のないもの
    • PDFを出力する過程や出力した後での状態の変化
    • 例えば、金額の計算結果の確認は PDF spec の責務ではない

そして、この責務に則り、PDF spec (PDF そのものの比較) でカバーすべきものと、PDF 生成の実装 (PDF生成の過程のロジックなど) の spec でカバーすべきものへ整理することで、PDF spec のテストケースを全種類で 480程度に圧縮しました。

目視では判別できない差分でテストが失敗する

稀に、差分の PDF をみても差分を確認できないケースが発生したことがありました。この手のビジュアルリグレッションテストではよくあるやつですね。

これは、diff-pdf の v0.4v0.5 で追加された --channel-tolerance--dpi オプションを使って、検知する差分の閾値を調整することで解決できます。

おわりに

今回は、Misoca の PDF を支える PDF spec について書きました。

PDF に問題があった場合の影響は大きく多岐に渡ります。PDF spec の整備によって、常に一定の PDF の品質が担保されことにより、PDF の変更に対しての心理的な安心感も確保されたことは非常に大きいと思います。

一方で、現在の仕組みは、CI だけでなく、手元でも手軽に実行でき非常に便利な反面、大量のPDFファイル(期待するPDF)をリポジトリに保存している*3 点や、通常の spec に比べるとどうしても重く遅い処理のため、CI 全体の時間・リソースを少なからず圧迫してしまっている点など、課題点も多く残るのが現状です。

今後も、reg-suit の導入など、より良い環境を模索していきたいと思っています。

お知らせ

弥生では一緒に働く仲間を募集しています!

herp.careers

*1:Misoca では基本的に毎日午前午後の2回リリースします。

*2:その他関連するものとして、PDFの比較を行う pdf_matcher gem や GitHub Actions で diff-pdf をインストールする setup-diff-pdf action などもあります。

*3:Git LFS として扱っています。

もくテク「新年の抱負を語ろう!LT」を開催しました

f:id:tatsuki_iida:20220124111415j:plain

こんにちは、情シスエンジニアの飯田です。

寒さの厳しい時期になりましたね。私の部屋はロフト付きのため天井が高く、エアコンの暖房が上に流れてしまうため冬の寒さが課題だったのですが、今年から電気毛布とフットウォーマーを導入してリモートワーク環境が快適になりました。電気代も安いみたいなので嬉しいです。

さて今回は、先日1月20日(木)に開催したもくテクの報告記事となります。
イベントページはこちら(開催は終了しています)。

mokuteku.connpass.com

テーマは「新年の抱負を語ろう!LT」

2022年最初の開催ということで、テーマは新年らしく今年の抱負についてです!
デスクトップ・クラウドアプリ開発に新規サービス開発、情シス、QAと様々なチームのエンジニアに登壇いただき、新年の抱負を発表してもらいました。

技術の話がメインになると思いきや、仕事に関することからプライベートのことまで様々な抱負や目標が語られましたので、カテゴリー分けしてご紹介したいと思います。

仕事で成果を出すぞ!

最初は仕事に関するものです。各チームで今年の開発スケジュールや山場が見えているので、そこで成果を出せるよう勉強を頑張りたいという発表がありました。

この目標に対する手段として挙げられるのが資格の取得で、情報処理技術者試験やAWS認定、さらには弥生らしく簿記を目指している方もいました。資格の勉強は知識の体系的な習得や整理に役立ち、見事合格できれば自信にも繋がるので、有効な方法だと思います。

趣味にも力を入れるぞ!

仕事も大切ですが、プライベートを充実させることも重要です。ということで積読を消化したり自作のアプリを作成したりと趣味に関する抱負もありました。

弥生の開発本部ではリモートワークがすっかり定着しており、通勤がなくなって自由に使える時間が増えたという人も多いと思います。コロナ禍という厳しい状況が続いていますが、その中でも楽しめる趣味があると生活も充実しますね。

生活習慣を変えるぞ!

仕事や趣味を問わず、それぞれ達成したい目標のために生活習慣を変えようという人もいました。エンジニアらしく自分で情報収集アプリを作ってチェックする習慣を身につけたり、ダイエットのために運動を始めたりと、こちらも手段は様々です。

そういえば弥生では2022年1月からフレックスタイム制が導入されました。リモートワークによって働く場所が選択できるようになり、フレックスによって働く時間にも自由度が生まれました。これを活かして家事や育児を頑張るという社員もおり、従来よりもより働きやすい環境になることが期待されます。

仕事に遊びごころを!

ちょっと変わった抱負もありました。ITエンジニアといえば設計書や手順書が欠かせませんが、お堅い文章ばかりなので時候の挨拶や超感覚的な表現を取り入れてほっこりしようという、遊びごころ満載の内容でした。正確さの求められるドキュメントで超感覚的とはこれ如何に?と思いましたが、発表では次のような例がありました。

  • 野山がサクラ色に染まり始める今日この頃、初期設定入力の画面設計です。
  • おいおい、あと何回させるつもりだ?とあきれる程、Windows Updateします。

なるほど、このような表現があると気持ちも和みそうですね!…あ、レビュアーさん、この人のドキュメントのレビューは念入りにお願いします(笑)

今年ももくテクを盛り上げていきます!

最後にもくテク運営の抱負を簡単に書きたいと思います。

もくテクは昨年5月にオンライン形式に変えて再開し、これまでで9回実施してきました。今年もトレンドの技術や話題を取り入れ、もくテクを盛り上げていきたいと思いますので、今後もよろしくお願いいたします!

え、私自身の抱負は何かって?…そろそろクロージングのお時間ですので今回はこの辺にしておきたいと思います(逃走)

次回のご案内

次回の開催は2月17日(木)で、「弥生って、実は機械学習もやってるんです!」がテーマです。

弥生が提供するサービスには機械学習を用いた機能があり、自社で研究開発を行っています。
どのような仕組みなのか、研究開発はどうやっているのか、運用はどうしているかなどの裏側について紹介しますので、機械学習に興味のある方はぜひご参加ください!

mokuteku.connpass.com

採用について

弥生では、幅広いポジションでいっしょに働く仲間を募集しています!
ポジション等の詳細については以下のサイトをご覧ください。

herp.careers

Dockerの有償プランを契約して運用するまでの話

こんにちは。弥生の内山です。
みなさまご存知の通り、2022年1月31日以降、一定以上の規模の組織でDocker Desktopを利用するには有償プランの契約が必要となります。

www.docker.com

弥生では、検討の結果、Dockerを利用する開発者全員分のライセンスを購入し運用することにしました。
しかし、実際に有償プランを使おう!となったとき、具体的に何をしたらよいのかという情報がWeb上にほとんど見つからず、手探りで作業を進めることになりました。
現時点で、ようやく運用の形が見えてきたため、この記事では契約から運用までの具体的な考慮ポイントをご紹介したいと思います。
無償利用の猶予期間終了が迫っている中、有償プランの利用について困っている方の助けになれば幸いです。

(なお、この記事は2022/01/20時点で弥生内で検討・試行した内容に基づいて作成したものであり、Docker社の提供するサービスについて正確な情報を提供することを目的としたものではありません。契約を行われる際は十分な調査を行った上でご判断ください)

なぜ有償プランを契約するのか

Docker Desktopが使いたい

有償プランを契約する目的は、開発メンバーがDocker Desktopを使えるようにすること、ほぼそれだけです。
「Docker Desktopを利用せず、無償で利用できるツールの範囲でDockerを使い続ければよいのでは?」という考え方もあります。
実際、Web上にはDocker Desktopを使わずにDockerを使い続けるための手順が多数見つかります。
そのようにして有償プランを契約せずにDockerを利用し続けるという選択はありだと思います。
ですが、我々は有償プランを契約してDocker Desktopを利用した方がよいと考えました。
理由は以下です。

Dockerまわりのセットアップやトラブルシュートが簡単であるため

Docker Desktopは、それをインストールするだけで、Dockerの利用に必要なセットアップがほとんど完了します。
また、GUIのツール上から、稼働中のコンテナやpullしたイメージを管理できるため、DockerのCUI操作に不慣れなメンバーでもトラブルシュートが比較的容易です。
実際の開発チームでは、スキルや経験が様々なメンバーが参加しているため、簡単にセットアップやトラブルシュートが可能であるというメリットは大きいと考えます。

Windowsコンテナを利用したい場合、Docker Desktopがないと厳しそうであるため

弥生の新規プロダクトの開発では、Windowsコンテナを利用して機能を実装する可能性があります。
Windows 10/11にてWindowsコンテナを利用する場合、Microsoft公式の手順では、Docker Desktopをインストールする方法が案内されています
Web上を探せば、Docker Desktopを利用せずにWindowsコンテナを利用する手順も見つかりはするのですが、多少複雑な手順となりますし、またいつまでもその手順が有効とは限りません。
前述の理由と近いですが、開発メンバーがトラブル無く開発を続けられることを重視するならば、やはり公式の手順通り、Docker DesktopをインストールしてWindowsコンテナを利用した方がよいと考えました。

契約・運用のポイント

プラン選択は"Team"プランがよさそう

2022年1月現在、Dockerの有償プランには、"Pro"、"Team"、"Business"という3つのプランがあります。
どのプランでもDocker Desktopは利用できるため、一番安い"Pro"プランを選択したくなるのですが、企業で利用する場合、"Team"プランを選択するのが現実的だと考えています。

www.docker.com

企業でソフトウェアライセンスを管理・運用する場合、「必要な分を一括で購入し、必要なメンバーに付与し、不要なメンバーからは回収する」というようなオペレーションができないと、管理が面倒すぎて運用が難しいと思います。
"Pro"プランは、個人の商用開発をターゲットとしているためか、Docker ID(=ユーザーアカウント)のそれぞれにおいて支払情報(クレカ)を紐付けて支払いを行うような設計になっているようです。
5人以下程度の組織ならばどうにか運用できるかもしれませんが、それ以上となると管理は難しいでしょう。
"Team"プランであれば、"Seats"という形でライセンスがプールされ、個別のメンバーに着脱を行うことができるため、数十人規模での運用に耐えうると思います。

f:id:ucho_yayoi:20220120201731p:plain
"Team"プランでは利用しているSeatsが表示される。Seatsの増減も可能。

”Business"プランは、SSO機能などがあるようなので、より大規模な組織での運用に適しているのではないかと思います。
ただし、"Team"プランと比較して価格が跳ね上がることと、我々としてはそこまで高度な機能を必要としていないことから、候補としてあまり検討を行いませんでした。"Business"プランは日本の代理店での取り扱いを行っているので、興味のある方はお問い合わせをしてみるとよいかもしれません。

メンバーには業務用アカウントを取得してもらう

"Team"プランでは、Organizationというグループのようなものを作成し、そこにメンバーを招待して追加していきます(追加されるとSeatsが消費されます)。
招待を行う際、Docker IDまたはメールアドレスを指定して招待を送るのですが、我々はここで社用のメールアドレス宛てに招待を送り、その社用メールアドレスを使ってDocker IDを作成してもらう運用としました。
理由は以下です。

ライセンスの利用者を明確にするため

メールアドレスを指定して招待を行うと、そのメールアドレスを紐付けたDocker IDでなければ招待元のOrganizationに参加することができないようです。
このことを利用し、招待は社用メールアドレスに対してのみ行うようにすることによって、社内の開発メンバーだけが確実に会社のOrganizationに参加するようにしました。

f:id:ucho_yayoi:20220120200954p:plain
招待されたときのDocker ID作成画面には招待を受け取ったメールアドレスを入力する欄がある

個人利用と業務利用を分けるため

個人用のDocker IDを会社のOrganizationに追加してしまうと、個人でDockerを利用している際に、誤ってOrganization内のリポジトリへの出し入れを行ってしまい、セキュリティリスクにつながる恐れがあるのではないかと考えました。
個人用と業務用のDocker IDを別にすることによって、上記のようなリスクをあらかじめ軽減できるのではないかと思います。

台帳でアカウントを管理する

せっかく社用のメールアドレスを指定して招待を行ったものの、Organizationに追加されたDocker IDに紐付いているメールアドレスを見ることはできません。見ることができるのはID文字列とプロフィールのFull Nameだけです。
そのため、「誰のIDか識別できるようにするために、ID文字列やプロフィールに本名を入れるようにしよう!」などとしたくなるのですが、ID文字列とプロフィールは公開情報となるため、本名をインターネットに公開したくないメンバーに対してそのようなことを強要するべきではないと思います…。 そこで我々は、招待したメンバーから実際に作成したID文字列を教えてもらい、それを氏名やメールアドレスと合わせてスプレッドシートで台帳管理するという、アナログな解決策を採ることにしました。
ただ、結局は退社したメンバーをOrganizationから削除するなどの棚卸し作業が必要になるため、どのみち台帳管理のようなことは必要になると思います。
(もしかしたら"Business"プランでSSOを利用すれば、認証サーバー側でIDを無効化するだけでOK、となるのかもしれませんが、詳細はわかりません…)

おわりに

Dockerの有償プランを具体的にどのように運用していくかをご紹介しました。
このような有償ライセンスは、ライセンス費用そのものに加えて、運用のコストがかかるのがつらいところです。
みなさまも、運用上の工夫ポイントなどがありましたら、ぜひ共有いただけるとうれしいです。

お知らせ

弥生では一緒に働く仲間を募集しています! herp.careers