11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

汎用的なActionをComposite Actionsとして作成し、ワークフローのコードを再利用する

11
Last updated at Posted at 2025-12-02

こんにちは。
LIFULLでエンジニアマネージャとシニアエンジニアをしているりくしるです。

この記事はLIFULL Advent Calendar 2025の3日目の記事になります。

はじめに

私の管理しているチームではリリースフローの効率化・自動化の取り組みをしていて、その中で多くのリポジトリから参照されるGithub ActionsとComposite Actionsを多く作成しています。リリースフローの改善についてご興味をお持ちいただけましたら以下の記事も合わせてご覧いただけますと幸いです。

上記の取り組みは多くのリポジトリで機能させることを目的に実施しています。

GitHub Actionsは非常に便利なCI/CDツールですが、複数のリポジトリで同じようなワークフローを管理していると、ロジックの修正時に全リポジトリへPRを出す必要があり、メンテナンスが大変になりがちです。

この記事では、そんな課題を解決するComposite Actionsについて、基本的な使い方からLIFULLでの実践的なテクニックまでご紹介します。

GitHub Actionsとは

GitHub Actionsは、GitHubが提供するCI/CD機能です。リポジトリ内の.github/workflowsディレクトリにYAMLファイルを配置することで、プッシュやプルリクエストなどのイベントをトリガーに自動化処理を実行できます。

よくある使用例

name: CI

on:
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: npm test
      
      - name: Lint code
        run: npm run lint
      
      - name: Check formatting
        run: npm run format:check

このような設定を各リポジトリに配置することで、設定したトリガーを元に自動テストやlinterを実行できます。

課題:同じロジックの重複管理

しかし、複数のリポジトリで同様のワークフローを使用している場合、以下のような問題が発生します:

  • メンテナンスコストの増大: ロジックを修正したい時、全リポジトリにPRを出す必要がある
  • バージョン管理の困難: どのリポジトリがどのバージョンのワークフローを使っているか把握しづらい
  • 品質のばらつき: 各リポジトリで微妙に異なる実装になりがち

非常に便利ですが、これだと多くのリポジトリに同じものを作る必要があり、ロジックを修正したいときに全リポジトリへPRを出すことになり大変ですよね?

Composite Actionsで課題を解決

上記の課題を解決するために用意されているのがComposite Actionsです。

Composite Actionsとは

Composite Actionsは、複数のステップをまとめて再利用可能なアクションとして定義できる機能です。一つのリポジトリでアクションを管理し、他のリポジトリから参照することで、ワークフローの一元管理が可能になります。

基本的な作成方法

Composite Actionsはリポジトリを作成して、action.yaml(またはaction.yml)ファイルで定義してワークフローを記載することで作成することができます。

name: 'My Composite Action'
description: '再利用可能なアクションの例'

inputs:
  github_token:
    description: 'GitHub Token'
    required: true
  target_branch:
    description: 'ターゲットブランチ'
    required: false
    default: 'main'

outputs:
  result:
    description: '処理結果'
    value: ${{ steps.process.outputs.result }}

runs:
  using: 'composite'
  steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Process
      id: process
      shell: bash
      run: |
        echo "Processing on branch: ${{ inputs.target_branch }}"
        echo "result=success" >> $GITHUB_OUTPUT

重要なポイント:

  • runs.using: 'composite'を指定
  • 各ステップにshellを明示的に指定
  • inputsoutputsで入出力を定義

利用方法

作成したComposite Actionsは、他のリポジトリから以下のように利用できます:

name: Use Composite Actions

on:
  pull_request:
    branches: [ main ]

jobs:
  example:
    runs-on: ubuntu-latest
    steps:
      - name: Run my composite action
        uses: {organization名}/{CompositeActionRepository名}@v1.0.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          target_branch: 'develop'

バージョン管理も可能で、@v1.0.0のようにタグを指定することで、安定したバージョンを利用できます。

LIFULLでのテクニカルな使い方

ここからは、実際のプロダクション環境で使用している、より実践的なテクニックをご紹介します。

1. 条件分岐による動的な処理制御

Composite Actions内で複雑な条件分岐を実装し、状況に応じて異なる処理を実行する例です。

name: 'Dynamic Flow Control'
description: 'PRの状態に応じて動的に処理を切り替える'

inputs:
  pull_number:
    description: 'Pull Request number'
    required: true
  production_branch:
    description: 'プロダクションブランチ名'
    required: false
    default: 'master'

runs:
  using: 'composite'
  steps:
    - name: Get PR information
      uses: actions/github-script@v7
      id: pr_info
      with:
        script: |
          const { data: pr } = await github.rest.pulls.get({
            owner: context.repo.owner,
            repo: context.repo.repo,
            pull_number: ${{ inputs.pull_number }},
          });
          core.setOutput('base_ref', pr.base.ref);
          core.setOutput('head_sha', pr.head.sha);
    
    - name: Check if reverse merge
      id: check_reverse
      uses: actions/github-script@v7
      with:
        script: |
          if ('${{ steps.pr_info.outputs.base_ref }}' === 'develop') {
            const { data: branch } = await github.rest.repos.getBranch({
              owner: context.repo.owner,
              repo: context.repo.repo,
              branch: '${{ inputs.production_branch }}',
            });
            
            if (branch.commit.sha === '${{ steps.pr_info.outputs.head_sha }}') {
              console.log('Reverse merge detected - skipping checks');
              core.setOutput('is_reverse_merge', 'true');
              return;
            }
          }
          core.setOutput('is_reverse_merge', 'false');
    
    - name: Run normal checks
      if: steps.check_reverse.outputs.is_reverse_merge == 'false'
      shell: bash
      run: |
        echo "Running normal validation checks..."
        # 通常のチェック処理

このパターンでは、PRがリバースマージ(本番ブランチから開発ブランチへの取り込み)かどうかを判定し、該当する場合は通常のチェックをスキップしています。

2. 他のComposite Actionの呼び出し

Composite Actionから別のComposite Actionを呼び出すことで、処理を階層化し、より保守性の高い構造を実現できます。

name: 'Orchestrator Action'
description: '複数のComposite Actionを組み合わせる'

inputs:
  github_token:
    description: 'GitHub Token'
    required: true
  labels:
    description: 'ラベルのリスト'
    required: true

runs:
  using: 'composite'
  steps:
    # 認証情報の取得
    - id: credentials
      uses: {organization名}/{credentialsリポジトリ名}@v1
      with:
        role: RepositoryWrite
    
    # ラベルの追加(内部に共通利用する別のComposite Action)
    - name: Add labels
      uses: {organization名}/{CompositeActionRepository名}/shared/add-labels@v1
      with:
        github_token: ${{ steps.credentials.outputs.token }}
        labels: ${{ inputs.labels }}
    
    # コメントの削除(内部に共通利用する別のComposite Action)
    - name: Remove old comments
      uses: {organization名}/{CompositeActionRepository名}/shared/remove-comments@v1
      with:
        github_token: ${{ steps.credentials.outputs.token }}
        comment_patterns: '古いコメント,削除対象'

この構造により、各機能を独立したアクションとして管理でき、組み合わせて使用することで複雑なワークフローを構築できます。

3. 動的な認証情報の管理

セキュリティを考慮し、必要に応じて認証情報を動的に取得・使用するパターンです。

name: 'Secure Action'
description: '認証情報を安全に扱う'

inputs:
  github_token:
    description: 'GitHub Token(オプション)'
    required: false

runs:
  using: 'composite'
  steps:
    # トークンが提供されていない場合のみ認証情報を取得
    - id: credentials
      uses: {organization名}/{credentialsリポジトリ名}@v1
      if: ${{ inputs.github_token == '' }}
      with:
        role: SomethingElse
    
    # 提供されたトークンまたは取得したトークンを使用
    - name: Execute with token
      uses: actions/github-script@v7
      with:
        github-token: ${{ inputs.github_token == '' && steps.credentials.outputs.token || inputs.github_token }}
        script: |
          // トークンを使用した処理
          const { data: repo } = await github.rest.repos.get({
            owner: context.repo.owner,
            repo: context.repo.repo,
          });
          console.log(`Repository: ${repo.full_name}`);

このパターンでは、外部からトークンが提供されない場合のみ内部で認証情報を取得し、柔軟性とセキュリティを両立しています。

4. 待機処理とタイミング制御

GitHub Actionsの非同期処理を考慮し、適切なタイミングで処理を実行するテクニックです。

name: 'Timing Control'
description: 'ラベル付与などの非同期処理を待つ'

runs:
  using: 'composite'
  steps:
    # ラベルが付与されるまで待機
    - name: Wait for labels
      shell: bash
      run: sleep 30
    
    - name: Check labels
      uses: actions/github-script@v7
      with:
        script: |
          const { data: issue } = await github.rest.issues.get({
            owner: context.repo.owner,
            repo: context.repo.repo,
            issue_number: context.issue.number,
          });
          
          const labels = issue.labels.map(label => label.name);
          console.log(`Current labels: ${labels.join(', ')}`);
          
          // ラベルに基づいた処理
          if (labels.includes('approved')) {
            console.log('Approval label found!');
          }

他のワークフローやアクションによるラベル付与などの処理が完了するのを待ってから、次の処理を実行することで、競合状態を回避しています。

5. 複雑な出力の連携

複数のステップ間で情報を受け渡し、最終的な判定を行うパターンです。

name: 'Complex Output Handling'
description: 'ステップ間で複雑なデータを連携'

outputs:
  validation_result:
    description: '検証結果'
    value: ${{ steps.final_check.outputs.result }}
  details:
    description: '詳細情報'
    value: ${{ steps.final_check.outputs.details }}

runs:
  using: 'composite'
  steps:
    - name: First check
      id: first
      shell: bash
      run: |
        # 最初のチェック
        echo "status=pass" >> $GITHUB_OUTPUT
        echo "count=5" >> $GITHUB_OUTPUT
    
    - name: Second check
      id: second
      shell: bash
      run: |
        # 2番目のチェック
        echo "status=pass" >> $GITHUB_OUTPUT
        echo "issues=0" >> $GITHUB_OUTPUT
    
    - name: Final check
      id: final_check
      shell: bash
      run: |
        first_status="${{ steps.first.outputs.status }}"
        second_status="${{ steps.second.outputs.status }}"
        
        if [ "$first_status" = "pass" ] && [ "$second_status" = "pass" ]; then
          echo "result=success" >> $GITHUB_OUTPUT
          echo "details=All checks passed" >> $GITHUB_OUTPUT
        else
          echo "result=failure" >> $GITHUB_OUTPUT
          echo "details=Some checks failed" >> $GITHUB_OUTPUT
        fi

このように、各ステップの出力を組み合わせて最終的な判定を行うことで、複雑な検証ロジックを実装できます。

まとめ

GitHub Actionsは非常に便利なツールですが、同様のロジックが複数リポジトリに渡ってしまうことも多いと思います。

このような課題に対してComposite Actionsは非常に有効な解決策です:

  • 一元管理: ロジックを一箇所で管理し、複数リポジトリから参照
  • バージョン管理: タグを使った安定したバージョン管理
  • 保守性の向上: 修正が必要な場合も一箇所を更新するだけ
  • 再利用性: 共通処理を部品化して組み合わせ可能

特に、複数のリポジトリで共通のCI/CDフローを運用している場合や、組織全体でワークフローの標準化を進めたい場合に、Composite Actionsの導入を検討してみてはいかがでしょうか。

参考リンク

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?