38 KiB
CI/CD Migration to Centralized Repositories
Purpose
This document outlines the plan and preparation steps for migrating CI/CD workflows to centralized repositories. The organization uses a dual-repository architecture:
Repository Architecture
.github-private (Private & Secure Centralization)
- Purpose: Sensitive/proprietary workflows and deployment logic
- Visibility: Private (organization members only)
- Content: Deployment workflows, release pipelines, sensitive scripts
- Use Case: Production deployments, security-sensitive operations
MokoStandards (Public Centralization)
- Purpose: Public/shareable workflows and best practices
- Visibility: Public (community accessible)
- Content: Quality checks, testing workflows, public templates
- Use Case: Open-source projects, community contributions, standardization
This dual approach provides:
- Security: Keep sensitive CI/CD logic and configurations private in
.github-private - Community: Share public workflows and standards via
MokoStandards - Reusability: Share common workflows across multiple repositories
- Maintainability: Centralize CI/CD updates in one location per type
- Organization: Separate CI/CD infrastructure from application code
- Flexibility: Choose appropriate repository based on workflow sensitivity
Current State
Workflows in .github/workflows/
php_quality.yml- PHP code quality checks (PHPCS, PHPStan, PHP Compatibility)release_pipeline.yml- Release and build pipelineci.yml- Continuous integration checksrepo_health.yml- Repository health monitoringversion_branch.yml- Version branch managementjoomla_testing.yml- Joomla-specific testingdeploy_staging.yml- Staging deployment
Scripts in scripts/
scripts/lib/- Shared Python libraries (common.py, extension_utils.py, joomla_manifest.py)scripts/release/- Release automation scriptsscripts/validate/- Validation scriptsscripts/run/- Runtime scripts
Migration Strategy
Phase 1: Preparation (Current)
Files to Keep in Main Repository:
- Simple trigger workflows that call reusable workflows
- Repository-specific configuration files
- Application scripts that are part of the product
Files to Move to Centralized Repositories:
To .github-private (Private):
- Deployment workflows (deploy_staging.yml, production deployments)
- Release pipelines with sensitive logic (release_pipeline.yml)
- Workflows containing proprietary business logic
- Scripts with deployment credentials handling
To MokoStandards (Public):
- Quality check workflows (php_quality.yml)
- Testing workflows (joomla_testing.yml)
- Public CI/CD templates and examples
- Shared utility scripts (extension_utils.py, common.py)
Phase 2: Structure Setup
Create both centralized repositories with appropriate structure:
.github-private Repository Structure:
.github-private/
├── .github/
│ └── workflows/
│ ├── reusable-release-pipeline.yml (sensitive)
│ ├── reusable-deploy-staging.yml (sensitive)
│ └── reusable-deploy-production.yml (sensitive)
├── scripts/
│ ├── deployment/
│ │ ├── deploy.sh
│ │ └── rollback.sh
│ └── release/
│ └── publish.py
└── docs/
├── WORKFLOWS.md
└── DEPLOYMENT.md
MokoStandards Repository Structure:
MokoStandards/
├── .github/
│ └── workflows/
│ ├── reusable-php-quality.yml (public)
│ ├── reusable-joomla-testing.yml (public)
│ ├── reusable-dolibarr-testing.yml (public)
│ └── reusable-security-scan.yml (public)
├── scripts/
│ ├── shared/
│ │ ├── extension_utils.py
│ │ ├── common.py
│ │ └── validators/
│ └── templates/
│ ├── workflow-templates/
│ └── action-templates/
└── docs/
├── STANDARDS.md
├── WORKFLOWS.md
└── CONTRIBUTING.md
Phase 3: Conversion
Main Repository Workflows (Simplified Callers):
Example 1: Public Quality Workflow (calls MokoStandards)
php_quality.yml:
name: PHP Code Quality
on:
pull_request:
branches: [ main, dev/*, rc/* ]
push:
branches: [ main, dev/*, rc/* ]
jobs:
quality:
uses: mokoconsulting-tech/MokoStandards/.github/workflows/reusable-php-quality.yml@v1
with:
php-versions: '["8.0", "8.1", "8.2", "8.3"]'
secrets: inherit
Example 2: Private Deployment Workflow (calls .github-private)
deploy.yml:
name: Deploy to Staging
on:
workflow_dispatch:
inputs:
environment:
required: true
type: choice
options: [staging, production]
jobs:
deploy:
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-deploy.yml@main
with:
environment: ${{ inputs.environment }}
platform: 'joomla'
secrets: inherit
Centralized Reusable Workflow Examples:
In MokoStandards (Public):
Located in MokoStandards/.github/workflows/reusable-php-quality.yml:
name: Reusable PHP Quality Workflow
on:
workflow_call:
inputs:
php-versions:
required: false
type: string
default: '["8.0", "8.1", "8.2", "8.3"]'
jobs:
# Full implementation here
In .github-private (Private):
Located in .github-private/.github/workflows/reusable-deploy.yml:
name: Reusable Deployment Workflow
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
FTP_SERVER:
required: true
FTP_USER:
required: true
jobs:
# Deployment logic here
Phase 4: Migration Steps
-
Create both centralized repositories
.github-private: Private repository (mokoconsulting-tech/.github-private)MokoStandards: Public repository (mokoconsulting-tech/MokoStandards)- Initialize each with README and LICENSE
- Set up appropriate branch protection rules
- Configure access: private (team only) and public (community)
-
Categorize and move workflows
-
Sensitive workflows →
.github-private:- deployment workflows, release pipelines
- Convert to reusable workflows with
workflow_calltriggers - Add security controls and audit logging
-
Public workflows →
MokoStandards:- quality checks, testing workflows
- Add comprehensive documentation and examples
- Enable community contributions
-
Test each workflow in isolation
-
Add proper input parameters and secrets handling
-
-
Update main repository workflows
- Replace with simplified caller workflows pointing to appropriate repository
- Update documentation with new workflow references
- Test integration end-to-end
-
Migrate shared scripts
- Deployment/sensitive scripts →
.github-private/scripts/ - Public utilities →
MokoStandards/scripts/shared/ - Keep product-specific scripts in main repo
- Update import paths and references
- Deployment/sensitive scripts →
-
Update documentation
- Document workflow calling conventions
- Update development guides
- Create troubleshooting guides
Configuration Requirements
Secrets to Configure
In .github-private repository:
- Deployment credentials (FTP_SERVER, FTP_USER, FTP_KEY, etc.)
- API tokens for external services
- Signing keys
In main repository:
- Inherit secrets from organization level
- Repository-specific overrides only
Variables to Configure
Organization-level variables:
- DEPLOY_DRY_RUN
- FTP_PATH_SUFFIX
- PHP_VERSIONS (default)
Repository-level variables:
- Repository-specific configurations
- Feature flags
Workflow Categorization
Workflows to Centralize
To MokoStandards (Public Repository)
-
php_quality.yml ✓
- Reason: Standardized quality checks across all PHP projects
- Type: Reusable workflow
- Sensitivity: Low (no secrets, publicly shareable)
- Target:
MokoStandards/.github/workflows/reusable-php-quality.yml
-
joomla_testing.yml ✓
- Reason: Shared across Joomla projects, community benefit
- Type: Reusable workflow
- Sensitivity: Low (testing patterns, no sensitive data)
- Target:
MokoStandards/.github/workflows/reusable-joomla-testing.yml
-
ci.yml (partially)
- Reason: Generic CI patterns can be shared
- Type: Reusable workflow template
- Sensitivity: Low (standard CI practices)
- Target:
MokoStandards/.github/workflows/reusable-ci-base.yml
To .github-private (Private Repository)
-
release_pipeline.yml ✓
- Reason: Complex release logic, contains sensitive deployment steps
- Type: Reusable workflow
- Sensitivity: High (deployment credentials, business logic)
- Target:
.github-private/.github/workflows/reusable-release-pipeline.yml
-
deploy_staging.yml ✓
- Reason: Contains deployment credentials and proprietary logic
- Type: Reusable workflow
- Sensitivity: High (FTP credentials, server details)
- Target:
.github-private/.github/workflows/reusable-deploy-staging.yml
-
deploy_production.yml ✓
- Reason: Production deployment with strict security requirements
- Type: Reusable workflow
- Sensitivity: Critical (production access)
- Target:
.github-private/.github/workflows/reusable-deploy-production.yml
Workflows to Keep Local (Main Repository)
-
ci.yml (project-specific parts)
- Reason: Repository-specific CI steps
- Can call centralized workflows from both
MokoStandardsand.github-private
-
repo_health.yml
- Reason: Repository-specific health checks and metrics
- Keep local with option to extend from
MokoStandardsbase
-
version_branch.yml
- Reason: Project-specific versioning strategy
- Keep local
Scripts Categorization
Scripts to Centralize
To MokoStandards (Public)
-
scripts/lib/extension_utils.py ✓
- Shared across all extension projects
- Platform detection logic (Joomla/Dolibarr)
- Target:
MokoStandards/scripts/shared/extension_utils.py
-
scripts/lib/common.py ✓
- Universal utility functions
- No project-specific or sensitive logic
- Target:
MokoStandards/scripts/shared/common.py
-
scripts/release/detect_platform.py ✓
- Platform detection helper
- Publicly useful for other projects
- Target:
MokoStandards/scripts/shared/detect_platform.py
To .github-private (Private)
-
scripts/release/deployment/ ✓
- Deployment scripts with credential handling
- Target:
.github-private/scripts/deployment/
-
scripts/release/publish.py (if sensitive)
- Release publishing with proprietary logic
- Target:
.github-private/scripts/release/publish.py
Scripts to Keep Local
-
scripts/lib/joomla_manifest.py
- Joomla-specific, but project may have customizations
- Evaluate based on actual usage
-
scripts/validate/ (most)
- Project-specific validation rules
- Keep local unless truly generic
-
scripts/release/package_extension.py
- Uses shared libraries but has project-specific logic
- Keep local, depend on centralized libs
Benefits After Migration
For Development Team
- ✅ Simplified workflow files in main repository
- ✅ Easier to understand and maintain
- ✅ Consistent CI/CD across all projects
- ✅ Faster updates (update once, applies everywhere)
For Security
- ✅ Sensitive credentials isolated in private repository
- ✅ Controlled access to deployment logic
- ✅ Audit trail for CI/CD changes
For Organization
- ✅ Centralized CI/CD governance
- ✅ Standardized processes across projects
- ✅ Reduced duplication
- ✅ Easier onboarding for new projects
Testing Plan
Pre-Migration Testing
- ✅ Document all current workflows and their triggers
- ✅ Identify all secrets and variables used
- ✅ Create inventory of external dependencies
During Migration
- Create .github-private repository in test organization first
- Convert one workflow at a time
- Test with feature branches
- Validate all trigger conditions work
- Verify secret inheritance
Post-Migration Validation
- Run full CI/CD pipeline on test branch
- Verify all workflows execute correctly
- Check deployment to staging
- Monitor for any broken integrations
- Update runbooks and documentation
Rollback Plan
If issues arise during migration:
- Immediate Rollback: Revert caller workflow to inline implementation
- Keep Both: Maintain both local and centralized workflows temporarily
- Gradual Migration: Move workflows one at a time with validation periods
Timeline
- Week 1: Create .github-private repository, set up structure
- Week 2: Convert and test php_quality.yml
- Week 3: Convert and test release_pipeline.yml and deploy_staging.yml
- Week 4: Convert remaining workflows, finalize documentation
- Week 5: Complete migration, monitor, and optimize
Action Items
Immediate (This PR)
- Create migration plan document
- Review and approve migration strategy
- Identify team members responsible for migration
Next Steps
- Create .github-private repository
- Set up repository structure
- Configure secrets and variables at organization level
- Begin workflow conversion (starting with php_quality.yml)
- Test reusable workflow pattern
- Document lessons learned
Technical Architecture
Communication Flow
┌─────────────────────────────────────────────────────────────┐
│ Main Repository (.github/workflows/) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Caller Workflow (php_quality.yml) │ │
│ │ - Defines triggers (push, PR, etc.) │ │
│ │ - Sets permissions │ │
│ │ - Passes inputs and secrets │ │
│ └───────────────────┬───────────────────────────────────┘ │
│ │ uses: org/.github-private/...@main │
└──────────────────────┼──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ .github-private Repository (.github/workflows/) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Reusable Workflow (reusable-php-quality.yml) │ │
│ │ - workflow_call trigger │ │
│ │ - Receives inputs from caller │ │
│ │ - Inherits secrets from organization │ │
│ │ - Executes CI/CD logic │ │
│ │ - Returns job outputs │ │
│ └───────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────▼───────────────────────────────────┐ │
│ │ Shared Scripts (scripts/shared/) │ │
│ │ - extension_utils.py │ │
│ │ - deployment utilities │ │
│ │ - validation helpers │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Secret and Variable Inheritance Model
Organization Level (Settings > Secrets and Variables)
├── Secrets
│ ├── FTP_SERVER (inherited by all repos)
│ ├── FTP_USER (inherited by all repos)
│ ├── FTP_KEY (inherited by all repos)
│ ├── FTP_PASSWORD (inherited by all repos)
│ ├── FTP_PATH (inherited by all repos)
│ └── API_TOKENS (inherited by all repos)
│
├── Variables
│ ├── DEPLOY_DRY_RUN: false (can be overridden)
│ ├── FTP_PROTOCOL: sftp (can be overridden)
│ ├── FTP_PORT: 22 (can be overridden)
│ └── PHP_VERSIONS: ["8.0","8.1","8.2","8.3"]
│
└── Repository Level (Override if needed)
├── moko-cassiopeia
│ └── Variables
│ └── DEPLOY_DRY_RUN: true (override for this repo)
│
└── other-project
└── Variables
└── FTP_PATH_SUFFIX: /custom (repo-specific)
Workflow Version Pinning Strategy
Option 1: Track Main Branch (Automatic Updates)
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@main
Pros: Always get latest features and fixes Cons: Breaking changes may affect workflows Use Case: Development branches, staging deployments
Option 2: Pin to Semantic Version (Stable)
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@v1
# or
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@v1.2
Pros: Stable, predictable behavior Cons: Manual updates required Use Case: Production deployments, critical workflows
Option 3: Pin to Specific Commit SHA (Maximum Stability)
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@a1b2c3d
Pros: Immutable, guaranteed consistency Cons: No automatic updates, harder to maintain Use Case: Compliance requirements, audit trails
Detailed Workflow Conversion Examples
Before: Inline Workflow (Current State)
.github/workflows/php_quality.yml (93 lines)
name: PHP Code Quality
on:
pull_request:
branches: [ main, dev/*, rc/* ]
push:
branches: [ main, dev/*, rc/* ]
permissions:
contents: read
jobs:
php-compatibility-check:
name: PHP Compatibility Check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, xml, curl, zip
- name: Install PHP_CodeSniffer and PHPCompatibility
run: |
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
composer global require "phpcompatibility/php-compatibility:^9.0" --with-all-dependencies
phpcs --config-set installed_paths ~/.composer/vendor/phpcompatibility/php-compatibility
- name: Check PHP 8.0+ Compatibility
run: phpcs --standard=PHPCompatibility --runtime-set testVersion 8.0- src/
phpcs:
name: PHP_CodeSniffer
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.0', '8.1', '8.2', '8.3']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml, curl, zip
- name: Install PHP_CodeSniffer
run: composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
- name: Run PHP_CodeSniffer
run: phpcs --standard=phpcs.xml src/
# ... additional jobs
After: Caller Workflow (Target State)
.github/workflows/php_quality.yml (15 lines - 84% reduction)
name: PHP Code Quality
on:
pull_request:
branches: [ main, dev/*, rc/* ]
push:
branches: [ main, dev/*, rc/* ]
permissions:
contents: read
jobs:
quality-checks:
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@v1
with:
php-versions: '["8.0", "8.1", "8.2", "8.3"]'
php-extensions: 'mbstring, xml, curl, zip'
source-directory: 'src'
phpcs-standard: 'phpcs.xml'
enable-phpcompat: true
enable-phpstan: true
phpstan-level: 'max'
secrets: inherit
Benefits:
- 84% reduction in code
- Centralized maintenance
- Consistent across all repositories
- Easy to add new checks (update once in .github-private)
- Version control with semantic versioning
Reusable Workflow (in .github-private)
.github-private/.github/workflows/reusable-php-quality.yml
name: Reusable PHP Quality Checks
on:
workflow_call:
inputs:
php-versions:
description: 'JSON array of PHP versions to test against'
required: false
type: string
default: '["8.0", "8.1", "8.2", "8.3"]'
php-extensions:
description: 'Comma-separated list of PHP extensions'
required: false
type: string
default: 'mbstring, xml, curl, zip'
source-directory:
description: 'Source code directory to analyze'
required: false
type: string
default: 'src'
phpcs-standard:
description: 'PHPCS standard configuration file'
required: false
type: string
default: 'phpcs.xml'
enable-phpcompat:
description: 'Enable PHP Compatibility checks'
required: false
type: boolean
default: true
enable-phpstan:
description: 'Enable PHPStan static analysis'
required: false
type: boolean
default: true
phpstan-level:
description: 'PHPStan analysis level'
required: false
type: string
default: 'max'
phpstan-config:
description: 'PHPStan configuration file'
required: false
type: string
default: 'phpstan.neon'
outputs:
phpcs-passed:
description: 'Whether PHPCS checks passed'
value: ${{ jobs.phpcs.outputs.passed }}
phpstan-passed:
description: 'Whether PHPStan checks passed'
value: ${{ jobs.phpstan.outputs.passed }}
permissions:
contents: read
jobs:
php-compatibility-check:
name: PHP Compatibility Check
runs-on: ubuntu-latest
if: ${{ inputs.enable-phpcompat }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: ${{ inputs.php-extensions }}
coverage: none
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: |
~/.composer/cache
~/.composer/vendor
key: ${{ runner.os }}-composer-phpcompat-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-phpcompat-
- name: Install PHP_CodeSniffer and PHPCompatibility
run: |
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
composer global require "phpcompatibility/php-compatibility:^9.0" --with-all-dependencies
phpcs --config-set installed_paths ~/.composer/vendor/phpcompatibility/php-compatibility
- name: Check PHP 8.0+ Compatibility
run: |
phpcs --standard=PHPCompatibility \
--runtime-set testVersion 8.0- \
--report=full \
--report-file=phpcompat-report.txt \
${{ inputs.source-directory }}/
- name: Upload PHPCompatibility Report
if: always()
uses: actions/upload-artifact@v4
with:
name: phpcompat-report
path: phpcompat-report.txt
retention-days: 30
phpcs:
name: PHP_CodeSniffer (PHP ${{ matrix.php-version }})
runs-on: ubuntu-latest
outputs:
passed: ${{ steps.check.outputs.passed }}
strategy:
fail-fast: false
matrix:
php-version: ${{ fromJson(inputs.php-versions) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: ${{ inputs.php-extensions }}
coverage: none
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: |
~/.composer/cache
~/.composer/vendor
key: ${{ runner.os }}-php${{ matrix.php-version }}-composer-phpcs-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php${{ matrix.php-version }}-composer-phpcs-
- name: Install PHP_CodeSniffer
run: composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
- name: Run PHP_CodeSniffer
id: check
run: |
phpcs --standard=${{ inputs.phpcs-standard }} \
--report=full \
--report-file=phpcs-report-${{ matrix.php-version }}.txt \
${{ inputs.source-directory }}/
echo "passed=true" >> $GITHUB_OUTPUT
- name: Upload PHPCS Report
if: always()
uses: actions/upload-artifact@v4
with:
name: phpcs-report-php${{ matrix.php-version }}
path: phpcs-report-${{ matrix.php-version }}.txt
retention-days: 30
phpstan:
name: PHPStan (PHP ${{ matrix.php-version }})
runs-on: ubuntu-latest
if: ${{ inputs.enable-phpstan }}
outputs:
passed: ${{ steps.check.outputs.passed }}
strategy:
fail-fast: false
matrix:
php-version: ${{ fromJson(inputs.php-versions) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: ${{ inputs.php-extensions }}
coverage: none
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: |
~/.composer/cache
~/.composer/vendor
key: ${{ runner.os }}-php${{ matrix.php-version }}-composer-phpstan-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php${{ matrix.php-version }}-composer-phpstan-
- name: Install PHPStan
run: composer global require "phpstan/phpstan:^1.0" --with-all-dependencies
- name: Run PHPStan
id: check
run: |
phpstan analyse \
--configuration=${{ inputs.phpstan-config }} \
--level=${{ inputs.phpstan-level }} \
--error-format=table \
--no-progress \
--no-interaction \
${{ inputs.source-directory }}/ \
> phpstan-report-${{ matrix.php-version }}.txt 2>&1
echo "passed=true" >> $GITHUB_OUTPUT
- name: Upload PHPStan Report
if: always()
uses: actions/upload-artifact@v4
with:
name: phpstan-report-php${{ matrix.php-version }}
path: phpstan-report-${{ matrix.php-version }}.txt
retention-days: 30
Advanced Patterns and Best Practices
Pattern 1: Conditional Workflow Execution
Allow repositories to enable/disable specific checks:
# Caller workflow
jobs:
quality:
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@v1
with:
enable-phpcompat: ${{ github.event_name == 'pull_request' }} # Only on PRs
enable-phpstan: ${{ contains(github.event.head_commit.message, '[phpstan]') }} # Only if commit message contains [phpstan]
phpstan-level: ${{ github.ref == 'refs/heads/main' && 'max' || '6' }} # Stricter on main
Pattern 2: Matrix Strategy Inheritance
Pass complex matrix configurations:
# Caller workflow
jobs:
test:
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-test.yml@v1
with:
test-matrix: |
{
"php": ["8.0", "8.1", "8.2", "8.3"],
"joomla": ["4.4", "5.0"],
"database": ["mysql:8.0", "postgresql:14"]
}
Pattern 3: Composite Actions for Reusability
Break down workflows into composite actions for even more reusability:
.github-private/.github/actions/setup-php-quality/action.yml
name: 'Setup PHP Quality Tools'
description: 'Install PHP CodeSniffer, PHPCompatibility, and PHPStan'
inputs:
php-version:
description: 'PHP version to setup'
required: true
enable-phpstan:
description: 'Install PHPStan'
required: false
default: 'true'
runs:
using: 'composite'
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
extensions: mbstring, xml, curl, zip
coverage: none
- name: Install PHPCS and PHPCompatibility
shell: bash
run: |
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
composer global require "phpcompatibility/php-compatibility:^9.0" --with-all-dependencies
phpcs --config-set installed_paths ~/.composer/vendor/phpcompatibility/php-compatibility
- name: Install PHPStan
if: inputs.enable-phpstan == 'true'
shell: bash
run: composer global require "phpstan/phpstan:^1.0" --with-all-dependencies
Usage in reusable workflow:
- name: Setup PHP Quality Tools
uses: mokoconsulting-tech/.github-private/.github/actions/setup-php-quality@v1
with:
php-version: ${{ matrix.php-version }}
enable-phpstan: true
Pattern 4: Workflow Outputs and Chaining
Use outputs to chain workflows:
# Caller workflow
jobs:
quality:
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@v1
with:
php-versions: '["8.0", "8.1", "8.2", "8.3"]'
deploy:
needs: quality
if: ${{ needs.quality.outputs.phpcs-passed == 'true' && needs.quality.outputs.phpstan-passed == 'true' }}
uses: mokoconsulting-tech/.github-private/.github/workflows/reusable-deploy.yml@v1
with:
environment: staging
Security Considerations
Principle of Least Privilege
Organization Secrets Access:
- Only grant
.github-privaterepository access to necessary secrets - Use environment-specific secrets (staging, production)
- Rotate secrets regularly
Repository Permissions:
# .github-private repository settings
Permissions:
- Read: All organization members (for viewing workflows)
- Write: DevOps team only
- Admin: Organization owners only
Branch Protection (main):
- Require pull request reviews (2 approvals)
- Require status checks to pass
- Require branches to be up to date
- No force pushes
- No deletions
Secret Masking
Ensure secrets are never exposed in logs:
# BAD - Exposes secret in logs
- name: Deploy
run: echo "Deploying with password: ${{ secrets.FTP_PASSWORD }}"
# GOOD - Secret is masked
- name: Deploy
run: |
echo "::add-mask::${{ secrets.FTP_PASSWORD }}"
./deploy.sh --password "${{ secrets.FTP_PASSWORD }}"
Audit Trail
Track all workflow executions:
# Add to all reusable workflows
- name: Audit Log
if: always()
run: |
echo "Workflow executed by: ${{ github.actor }}"
echo "Repository: ${{ github.repository }}"
echo "Branch: ${{ github.ref }}"
echo "Commit: ${{ github.sha }}"
echo "Workflow: ${{ github.workflow }}"
echo "Run ID: ${{ github.run_id }}"
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
Performance Optimization
Caching Strategy
Composer Dependencies:
- name: Cache Composer
uses: actions/cache@v4
with:
path: |
~/.composer/cache
~/.composer/vendor
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
Tool Installations:
- name: Cache Quality Tools
uses: actions/cache@v4
with:
path: |
~/.composer/vendor/squizlabs/php_codesniffer
~/.composer/vendor/phpstan/phpstan
key: ${{ runner.os }}-php-tools-v1
Parallel Execution
Maximize parallelism:
strategy:
fail-fast: false # Don't stop other jobs if one fails
max-parallel: 10 # Run up to 10 jobs simultaneously
matrix:
php-version: ['8.0', '8.1', '8.2', '8.3']
joomla-version: ['4.4', '5.0']
Job Concurrency Control
Prevent wasted resources:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} # Cancel old runs except on main
Monitoring and Observability
Workflow Status Notifications
Slack Integration:
- name: Notify Slack on Failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Workflow failed: ${{ github.workflow }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Workflow*: ${{ github.workflow }}\n*Repository*: ${{ github.repository }}\n*Branch*: ${{ github.ref }}\n*Actor*: ${{ github.actor }}\n*Run*: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Metrics Collection
Track workflow execution metrics:
- name: Record Metrics
if: always()
run: |
curl -X POST "${{ secrets.METRICS_ENDPOINT }}" \
-H "Content-Type: application/json" \
-d '{
"workflow": "${{ github.workflow }}",
"repository": "${{ github.repository }}",
"status": "${{ job.status }}",
"duration": "${{ steps.start-time.outputs.elapsed }}",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}'
Troubleshooting Guide
Common Issues and Solutions
Issue 1: "Workflow not found" error
Symptom:
Error: Unable to resolve action `mokoconsulting-tech/.github-private/.github/workflows/reusable-php-quality.yml@main`,
unable to find version `main`
Solutions:
- Verify
.github-privaterepository exists and is accessible - Check repository permissions (must have at least read access)
- Verify branch name (main vs master)
- Ensure workflow file exists at specified path
Verification Commands:
# Check repository access
gh api repos/mokoconsulting-tech/.github-private
# List workflow files
gh api repos/mokoconsulting-tech/.github-private/contents/.github/workflows
# Check branch exists
gh api repos/mokoconsulting-tech/.github-private/branches/main
Issue 2: Secrets not inherited
Symptom:
Error: Secret FTP_PASSWORD is not set
Solutions:
- Ensure
secrets: inheritis set in caller workflow - Verify secret exists at organization level
- Check
.github-privaterepository has access to organization secrets - Verify secret names match exactly (case-sensitive)
Verification:
# List organization secrets
gh api orgs/mokoconsulting-tech/actions/secrets
# Check repository secret access
gh api repos/mokoconsulting-tech/.github-private/actions/secrets
Issue 3: Workflow runs on wrong trigger
Symptom: Workflow runs when it shouldn't, or doesn't run when expected
Solutions:
- Review
on:triggers in caller workflow - Check branch protection rules
- Verify path filters if using
paths:orpaths-ignore: - Test with different trigger events
Example Fix:
# Before (runs on all pushes)
on:
push:
# After (runs only on main and feature branches)
on:
push:
branches:
- main
- 'feature/**'
Issue 4: Matrix strategy not working
Symptom: Only one job runs instead of multiple matrix jobs
Solutions:
- Verify JSON syntax in matrix definition
- Use
fromJson()for string inputs - Check for empty arrays
- Validate matrix variable references
Example:
# Caller workflow
with:
php-versions: '["8.0", "8.1", "8.2"]' # Must be JSON string
# Reusable workflow
matrix:
php-version: ${{ fromJson(inputs.php-versions) }} # Convert to array
References
- GitHub Reusable Workflows Documentation
- GitHub Security Best Practices
- Workflow Syntax
- workflow_call Event
- GitHub Actions Caching
- Composite Actions
Support
For questions or issues during migration:
- Review this document
- Check GitHub Actions documentation
- Contact: DevOps team
- Slack: #devops-support
Status: Ready for Implementation
Author: GitHub Copilot
Date: 2026-01-05
Version: 2.0
Last Updated: 2026-01-05