1234 lines
38 KiB
Markdown
1234 lines
38 KiB
Markdown
# 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`:
|
|
```yaml
|
|
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`:
|
|
```yaml
|
|
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`:
|
|
```yaml
|
|
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`:
|
|
```yaml
|
|
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
|
|
|
|
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_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)
|
|
|
|
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)
|
|
- [x] 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)
|
|
```yaml
|
|
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)
|
|
```yaml
|
|
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)
|
|
```yaml
|
|
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)**
|
|
```yaml
|
|
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)**
|
|
```yaml
|
|
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`**
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
# 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:
|
|
|
|
```yaml
|
|
# 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`**
|
|
```yaml
|
|
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:**
|
|
```yaml
|
|
- 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:
|
|
|
|
```yaml
|
|
# 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**:
|
|
```yaml
|
|
# .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:
|
|
|
|
```yaml
|
|
# 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:
|
|
|
|
```yaml
|
|
# 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**:
|
|
```yaml
|
|
- 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**:
|
|
```yaml
|
|
- 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:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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**:
|
|
```yaml
|
|
- 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:
|
|
|
|
```yaml
|
|
- 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```yaml
|
|
# 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**:
|
|
```yaml
|
|
# 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](https://docs.github.com/en/actions/using-workflows/reusing-workflows)
|
|
- [GitHub Security Best Practices](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
|
|
- [Workflow Syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
|
|
- [workflow_call Event](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_call)
|
|
- [GitHub Actions Caching](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
|
|
- [Composite Actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action)
|
|
|
|
## 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
|