Files
MokoCassiopeia/docs/CI_MIGRATION_PLAN.md

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/

  1. php_quality.yml - PHP code quality checks (PHPCS, PHPStan, PHP Compatibility)
  2. release_pipeline.yml - Release and build pipeline
  3. ci.yml - Continuous integration checks
  4. repo_health.yml - Repository health monitoring
  5. version_branch.yml - Version branch management
  6. joomla_testing.yml - Joomla-specific testing
  7. deploy_staging.yml - Staging deployment

Scripts in scripts/

  • scripts/lib/ - Shared Python libraries (common.py, extension_utils.py, joomla_manifest.py)
  • scripts/release/ - Release automation scripts
  • scripts/validate/ - Validation scripts
  • scripts/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_HOST:
        required: true
      FTP_USER:
        required: true

jobs:
  # Deployment logic here

Phase 4: Migration Steps

  1. 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)
  2. Categorize and move workflows

    • Sensitive workflows → .github-private:

      • deployment workflows, release pipelines
      • Convert to reusable workflows with workflow_call triggers
      • 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

  3. Update main repository workflows

    • Replace with simplified caller workflows pointing to appropriate repository
    • Update documentation with new workflow references
    • Test integration end-to-end
  4. 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
  5. Update documentation

    • Document workflow calling conventions
    • Update development guides
    • Create troubleshooting guides

Configuration Requirements

Secrets to Configure

In .github-private repository:

  • Deployment credentials (FTP_HOST, 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)

  1. 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
  2. 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
  3. 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)

  1. 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
  2. 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
  3. 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)

  1. ci.yml (project-specific parts)

    • Reason: Repository-specific CI steps
    • Can call centralized workflows from both MokoStandards and .github-private
  2. repo_health.yml

    • Reason: Repository-specific health checks and metrics
    • Keep local with option to extend from MokoStandards base
  3. version_branch.yml

    • Reason: Project-specific versioning strategy
    • Keep local

Scripts Categorization

Scripts to Centralize

To MokoStandards (Public)

  1. scripts/lib/extension_utils.py

    • Shared across all extension projects
    • Platform detection logic (Joomla/Dolibarr)
    • Target: MokoStandards/scripts/shared/extension_utils.py
  2. scripts/lib/common.py

    • Universal utility functions
    • No project-specific or sensitive logic
    • Target: MokoStandards/scripts/shared/common.py
  3. scripts/release/detect_platform.py

    • Platform detection helper
    • Publicly useful for other projects
    • Target: MokoStandards/scripts/shared/detect_platform.py

To .github-private (Private)

  1. scripts/release/deployment/

    • Deployment scripts with credential handling
    • Target: .github-private/scripts/deployment/
  2. scripts/release/publish.py (if sensitive)

    • Release publishing with proprietary logic
    • Target: .github-private/scripts/release/publish.py

Scripts to Keep Local

  1. scripts/lib/joomla_manifest.py

    • Joomla-specific, but project may have customizations
    • Evaluate based on actual usage
  2. scripts/validate/ (most)

    • Project-specific validation rules
    • Keep local unless truly generic
  3. 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

  1. Document all current workflows and their triggers
  2. Identify all secrets and variables used
  3. Create inventory of external dependencies

During Migration

  1. Create .github-private repository in test organization first
  2. Convert one workflow at a time
  3. Test with feature branches
  4. Validate all trigger conditions work
  5. Verify secret inheritance

Post-Migration Validation

  1. Run full CI/CD pipeline on test branch
  2. Verify all workflows execute correctly
  3. Check deployment to staging
  4. Monitor for any broken integrations
  5. Update runbooks and documentation

Rollback Plan

If issues arise during migration:

  1. Immediate Rollback: Revert caller workflow to inline implementation
  2. Keep Both: Maintain both local and centralized workflows temporarily
  3. 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_HOST                    (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-private repository 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:

  1. Verify .github-private repository exists and is accessible
  2. Check repository permissions (must have at least read access)
  3. Verify branch name (main vs master)
  4. 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:

  1. Ensure secrets: inherit is set in caller workflow
  2. Verify secret exists at organization level
  3. Check .github-private repository has access to organization secrets
  4. 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:

  1. Review on: triggers in caller workflow
  2. Check branch protection rules
  3. Verify path filters if using paths: or paths-ignore:
  4. 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:

  1. Verify JSON syntax in matrix definition
  2. Use fromJson() for string inputs
  3. Check for empty arrays
  4. 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

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