前回のブログでは、新しくOrbを作成するときの理想的な自動 CI/CD プロセスを設定する方法について説明しました。プロセスを自動化する目的は、バージョン管理、多層テスト、および自動的なデプロイメントを実現することです。それでは、例を見てみましょう。先月 CircleCI が公開した Azure CLI Orbをご覧ください。小規模で簡易な Orb ですので、簡単に理解できますが、サードパーティのクラウドプロバイダーである Microsoft Azure との統合であるため、複雑な部分もあります。

このプロセスでビルド、テスト、デプロイされた Orb の config.yml ファイルはそれほど複雑ではありません。CircleCI のOrb Toolsという Orb は、Orb 開発を自動化するための基本的なほぼすべての仕組みを抽象化しています。Azure CLI Orb の config.yml には、2 つの異なるワークフローが含まれます。1 つは、基本的な検証と開発版公開のためのワークフローであり、もう 1 つは、統合/使用テストと製品版の Orb のデプロイメント(可能な場合)のためのワークフローです。

workflows:
  lint_pack-validate_publish-dev:

  # …

  integration_tests-prod_deploy:

この設定により、単一の git コミットに関連付けられた単一のリポジトリにある単一の config.yml ファイル内で Orb の統合/使用テストを実行できます。これにより、他の Orb テストでなされている職人技のようなテスト方法を回避できます。Orbのインライン展開、テストのための外部リポジトリの使用、また、マシンエグゼキューターを使用して、CircleCI 上でローカルジョブとして新しい Orb のソースを評価する、などのこれらのテストは効果がないわけではありませんが、面倒を引き起こす場合があります。

デフォルトでは、config.yml ファイルで定義されたすべてのワークフローが同時に実行されます。しかし、ここでは、1 つのワークフローがコミットごとに実行され、もう 1 つのワークフローがgit タグによってのみトリガーされるように設定しました。これにより git タグを作るか作らないか選ぶことで、基本的な lint と検証を通りリリースされた開発版の Orbs にのみ、統合テストのワークフローを実行できます。

ワークフロー 1:

lint_pack-validate_publish-dev

この最初のワークフローは完全に Orb 化されています。つまり、Orb Tools という Orb で定義される一連のジョブを呼び出して必要なパラメーターを渡すこと以外に、自分の Orb リポジトリのコンフィグファイルで何も操作する必要はありません。これらのジョブを簡単に見ていきましょう。

  - orb-tools/lint:

orb-tools/lint ジョブは、singapore/lint-condo の Docker イメージによってパッケージ化された yamllint CLI ツールを使用して、指定されたディレクトリ内のすべての YAML ファイルを lint します。独自の .yamllint コンフィグファイルを提供することも、ジョブに含まれるいくつかの基本的なデフォルト設定を使用することもできます。

  - orb-tools/pack:
      requires:
        - orb-tools/lint

orb-tools/pack は、非構造化 YAML の形式で Orb を書くユーザー向けに作られていますが、利用することを強くお勧めします。1 つまたは 2 つ以上の個別のコマンドやジョブが Orb に含まれる場合、この方法で Orb のソースを整理すると、コード解析、デバッグ、開発が格段に簡単になります。これは、モノリスアプリケーションをより小さく、モジュール化したサービスに分割したり、HTML、JavaScript、CSS コードが散在する単一の HTML ファイルではなく、React コンポーネントを記述したりすることに似ています。

Orbs は、いくつかの上位レベルのオブジェクトタイプ(コマンド、エグゼキューター、ジョブ、サンプルなど)が使われており、それらがそのまま直感的にファイルツリーに格納できるよう設計されています。各オブジェクトタイプ毎のフォルダ配下に、そのタイプのすべてのインスタンスが含まれ、各インスタンスがそれぞれ YAML ファイルとなります。単一の @orb.yml ファイルは、バージョン情報と Orb の説明文が含まれる、このシステムの「エントリーポイント」として機能します。Orb が参照する他の Orb もここに置くことができます。参照する他の Orb は、orbs ディレクトリ配下に別個のファイルを作り指定することもできます。Azure CLI Orb の例の /src ディレクトリをご覧いただくと、これらがどのように見えるのかを簡単に理解できます。

デフォルトでは、orb-tools/pack は次の処理を実行します。

  1. プロジェクトをチェックアウトする。
  2. Orb のソースフォルダを単一の orb.yml ファイルにまとめる。
  3. この新しい orb.yml ファイルを検証する(circleci orb validate orb.yml)。
  4. orb.yml ファイルをワークスペースに保存し、ダウンストリームジョブで使用できるようにする

上記の YAML の断片に示されているように、このジョブは誰でも簡単に呼び出せるようにデフォルトでは引数なしで呼び出せて、細かいことは orb-tools がうまくやってくれるようになっています。

- orb-tools/publish-dev:
    orb-name: circleci/azure-cli
    context: orb-publishing
    requires:
      - orb-tools/pack

最初のワークフローのほぼ終わりまできました。このジョブは、この前のジョブによって YAML が lint 済みで、検証もされたOrb の開発版を、単純に公開するだけです。繰り返しになりますが、適切なデフォルト値が設定されているため、orb-name 引数の値を指定することが必要なだけであったりします。Orb を公開する場合、CircleCI API トークンが必要なため、このジョブにコンテキストを添付していますが、ジョブをプロジェクト環境変数として保存していた場合、コンテキストも不要になる場合があります。

- orb-tools/trigger-integration-workflow:
    name: trigger-integration-dev
    context: orb-publishing
    ssh-fingerprints: 23:d1:63:44:ad:e7:1a:b0:45:5e:1e:e4:49:ea:63:4e
    requires:
      - orb-tools/publish-dev
    filters:
      branches:
        ignore: master

- orb-tools/trigger-integration-workflow:
    name: trigger-integration-master
    context: orb-publishing
    ssh-fingerprints: 23:d1:63:44:ad:e7:1a:b0:45:5e:1e:e4:49:ea:63:4e
    tag: master
    requires:
      - orb-tools/publish-dev
    filters:
      branches:
        only: master

ここから少し複雑になります。Orb の基本的な検証はすべて終了し、Orb の開発版を公開しました。ここでは2番目のワークフローをトリガーしたいのですが、このワークフローは git タグが orb リポジトリにプッシュバックされた場合にのみ実行されます。これこそが、まさにこの orb-tools の出番なのです。このジョブで、引数として SSH フィンガープリントが必要なのはこのためです。このジョブは git タグを作成し、SSH を介して orb リポジトリにプッシュします。

CircleCI のドキュメントでは、キーを作成して CircleCI に追加するプロセスを詳しく説明していますが、簡単に説明すると、パスワードなしの OpenSSL(OpenSSH ではない)キーペアを生成し、公開鍵の部分を読み取り/書き込みのキーとして、GitHub や Bitbucket リポジトリに保存し、CircleCI に秘密鍵の部分を保存します。これにより、CircleCI ジョブがユーザーのリポジトリを変更できるようになります(例:git タグのプッシュ)。

内部的には、CircleCI のビルドシステムは、Orbs などのコンフィグファイルを実行時に処理するため、このようなアプローチが必要となります。したがって、新しい Webhook イベント(git タグなど)がなければ、手動で 2 つ目のコミットをプッシュしなければ、新しい開発バージョンの Orb をテストできません。git タグにより、config.yml ファイルが再度処理され、公開されたばかりの開発版の Orb を取得します。

重要なのは、ローカルマシンからプッシュした最初のコミットと同じコミットに git タグが付加されているため、この新しい CircleCI ビルドは同じ VCS ステータスチェックの一部として認識されることです。つまり、特定のプルリクエストに対して、両方のワークフローのジョブが同じコミットに関連付けられるため、開発者は特定のコード変更を簡単に評価できるようになります。

trigger-integration-workflow のジョブは 2 回呼び出されていることにお気付きでしょう。これは、integration-testing ワークフローは、正規表現フィルタリングを使用して、git タグのコンテンツに応じて、どのジョブを実行するかを制御するためです。これにより、以下が可能になります。

  1. マスター以外のブランチにおいて、コミットがプッシュされるたびに、統合テストジョブのみを実行できます。一方、マスターブランチにコミットされた場合は統合テストジョブの実行に加え、そのテストが成功した場合にのみi製品版のデプロイメントをトリガーすることができます。
  2. コミットにおける Orb ソースコードの変更部分に応じて、パッチ、マイナー、またはメジャーリリースのいずれかを動的に公開できます(例:新しいコマンドまたはジョブを加えたなら、メジャーリリースをトリガーするかもしれません。エグゼキューターが新しくなったり Orb の説明欄が変更されたりする場合には、マイナーリリースのみをトリガーするなど)。この機能はデフォルトで有効になっていますが、さまざまな種類のリリースタイミングを詳細に手動で制御したい場合は無効にできます(use-git-diff パラメーターを false に設定します)。

マスターへのコミットでのみ実行される 2 回目の trigger-integration-workflow ジョブは、基本的に文字列「master」を git タグに追加し、製品版のリリースジョブを含むワークフロー2を実行します。

ワークフロー 2:

integration-tests_prod-deploy

Orb 統合テストは、最初のワークフローよりも自由な形式になります。基本的には、どの Orb リポジトリの CircleCI コンフィグファイルにでもドラッグアンドドロップできます。多数の(そして拡大し続けている!)Orbs の管理者としては、基本的には使用テストに集中し、無理のない範囲でできる限り多くのエッジケースをカバーすることを目指しています。

言い換えるなら、前述したワークフローで公開したばかりの開発版の Orb を使用して、含まれるすべてのコマンドとジョブを実行します。これは、Orb に含まれるすべてのコマンドとジョブが失敗しないことと、、期待通りの動作をすることを確認するために行います。コマンドまたはジョブがいくつもの異なる方法で使用できる場合は、すべての使用方法がカバーできるように複数回実行します。コマンドまたはジョブがサードパーティサービス(この場合は Microsoft Azure)とやりとりする場合、テスト環境をセットアップして、Orb が他のユーザーに対しても機能することを確認します。

この Azure CLI Orb のアプローチを使用して、異なるランタイム環境で同じコマンドまたはジョブをテストすることもできます(ジョブがカスタムエグゼキューターを使用するように設定されている場合)。以下では、Go ベースの Docker イメージ、Python ベースの Docker イメージ、Microsoft 独自のファーストパーティ Azure Docker イメージを使用した、この Orb コマンドのテストを確認していきます。

ワークフロー2の YAML 全体を記しますが、このサンプルの詳細は他の Orbs には適用できない可能性があるため、製品版リリースジョブについて説明する前に、いくつかのポイントを簡単に説明しておきます。Orb Tools という「Orb」には、製品版リリースジョブが抽象化されて取り込まれており、同じような開発プロセスの実装を検討しているすべての Orb 開発者が使用できるように設計されています。

統合テストジョブで使用される YAML アンカーフィルター:

integration-dev_filters: &integration-dev_filters
  branches:
    ignore: /.*/
  tags:
    only: /integration-.*/

integration-master_filters: &integration-master_filters
  branches:
    ignore: /.*/
  tags:
    only: /master-.*/

prod-deploy_requires: &prod-deploy_requires
  [test-orb-python_master, test-orb-azure_master, test-orb-golang_master]

全ての統合テストとデプロイメントのワークフロー:

integration-tests_prod-deploy:
  jobs:
    # triggered by non-master branch commits
    - test-orb-python:
        name: test-orb-python_dev
        context: orb-publishing
        filters: *integration-dev_filters

    - test-orb-azure-docker:
        name: test-orb-azure_dev
        context: orb-publishing
        filters: *integration-dev_filters

    - test-orb-golang:
        name: test-orb-golang_dev
        context: orb-publishing
        filters: *integration-dev_filters

    # triggered by master branch commits
    - test-orb-python:
        name: test-orb-python_master
        context: orb-publishing
        filters: *integration-master_filters

    - test-orb-azure-docker:
        name: test-orb-azure_master
        context: orb-publishing
        filters: *integration-master_filters

    - test-orb-golang:
        name: test-orb-golang_master
        context: orb-publishing
        filters: *integration-master_filters

    # patch, minor, or major publishing
    - orb-tools/dev-promote-prod:
        name: dev-promote-patch
        orb-name: circleci/azure-cli
        context: orb-publishing
        requires: *prod-deploy_requires
        filters:
          branches:
            ignore: /.*/
          tags:
            only: /master-patch.*/

    - orb-tools/dev-promote-prod:
        name: dev-promote-minor
        orb-name: circleci/azure-cli
        release: minor
        context: orb-publishing
        requires: *prod-deploy_requires
        filters:
          branches:
            ignore: /.*/
          tags:
            only: /master-minor.*/

    - orb-tools/dev-promote-prod:
        name: dev-promote-major
        orb-name: circleci/azure-cli
        release: major
        context: orb-publishing
        requires: *prod-deploy_requires
        filters:
          branches:
            ignore: /.*/
          tags:
            only: /master-major.*/

さまざまな test-orb ジョブの中身は、この Orb の config.yml ファイルの前段階で定義されています。興味があれば、ぜひご覧ください。ここでは、YAML アンカーの使用についてのみ確認してください。YAML アンカーを使用すると、ブランチ/タグフィルタが簡素化されます。これらのフィルタを使用して、最初のワークフローでプッシュした git タグによって、integration-testing ジョブが製品版のリリースにつなげるかどうかを制御します。

最も重要なのは、3 回別々に呼び出す orb-tools/dev-promote-prod ジョブです。

このジョブを 3 回呼び出す理由とは何でしょうか?

このジョブは、開発版の Orb を製品版のセマンティックバージョンに昇格するように設計されています。そのため、昇格されたバージョンがパッチ(0.0.x)、マイナー(0.x.0)リリース、メジャー(x.0.0)リリースのいずれであるかを決定する引数が必要です。trigger-integration-workflow ジョブにおいて git タグを用いてどのタイプのリリースで最終的に公開するかを決定するために、このジョブを毎回異なる正規表現フィルタリングを用いて、 3 回呼び出す必要があります。

それ以外は、このジョブは非常に簡潔です。書かなければいけないコードをできるだけ少なくするように設計されています。最低限必要なのは、Orb の名前と CircleCI API トークンだけです。リリースタイプが指定されていない場合、デフォルトでパッチリリースになります。

まとめ

以上で、Orb の自動開発ワークフローの説明は終了です。Orb のソースコードを含むリポジトリに一度コミットするだけで、Linting、Orb の基本的な検証、開発版 Orb の公開、git タグを使用した 2 番目の integration-testing ワークフローのトリガー、Orb のバージョンをパッチ、マイナー、またはメジャーリリースとして動的に設定して製品版のリリースをする処理を行いことができるようになりました。

注記:

このリンクされたプロセスの例では、@dev:alpha タグを Orb に使用しています。製品版の Orb バージョンは変更不可能ですが、開発版は上書きが可能です。これにより、この Orb の古い @dev:alpha バージョンでCircleCI コンフィグを一度走らせ、新しい @dev:alpha バージョンを使用して、git タグをプッシュした後に再度処理を走らせることができます。したがって、CircleCI コンフィグを使えるようにするために、最初の @dev:alpha Orb は手動で公開し、このプロセスを「ブートストラップ」する必要があります。

また、以前の @dev:alpha には存在しなかったジョブやコマンドが呼び出されるなどの変更が Orb ソースにある場合など、特定のコミットをプッシュする前に @dev:alpha のバージョンを再度公開する必要がある場合もあります。CircleCI コンフィグが処理できない場合、CircleCI ジョブは実行されません。また、まだ存在しない Orb コマンドを呼び出すと、CircleCI コンフィグの処理においてエラーが発生します。

ここで説明しているテストジョブのほとんどは Orb Tools にあります。このプロセスの詳細な例を、このリポジトリの README に追加してあります。

最後まで読んでいただき、ありがとうございました。


その他の資料: