こんにちは。
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を明示的に指定 -
inputsとoutputsで入出力を定義
利用方法
作成した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の導入を検討してみてはいかがでしょうか。