diff --git a/.gitlint b/.gitlint new file mode 100644 index 0000000000..d66c8302cd --- /dev/null +++ b/.gitlint @@ -0,0 +1,50 @@ +# Gitlint configuration for CI Framework +# Enforces Conventional Commits specification +# See: https://www.conventionalcommits.org/ + +[general] +# Ignore merge commits, fixup commits, squash commits, and revert commits +ignore=merge,fixup,squash,revert + +# Enable all default rules +contrib=contrib-title-conventional-commits,contrib-body-requires-signed-off-by + +# Set the extra-path to allow for additional user-defined rules +# We enforce CI-FMW specific rules there +extra-path=gitlint_rules + +[title-must-not-contain-word] +# Don't allow these words in titles +words=WIP,wip + +[title-match-regex] +# Enforce conventional commits format: type(scope): description +# Examples: feat(auth): add login, fix: resolve bug, docs: update readme +regex=^(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|deprecate)(\(.+\))?(!)?: .{1,50}$ + +[body-max-line-length] +line-length=100 + +[body-min-length] +min-length=0 + +# Conventional commits plugin configuration +[contrib-title-conventional-commits] +# List of conventional commit types we allow +types=build,ci,docs,feat,fix,perf,refactor,style,test,chore,revert,deprecate + +# Optional scopes (if not specified, any scope is allowed) +# scopes=api,cli,core,docs,tests,ci + +# Breaking changes +# We allow ! at the end of type to indicate breaking changes +# Examples: feat!: breaking change, fix(auth)!: breaking auth fix + +# CI Framework custom deprecation rules configuration +[CIFMW-CR1] +# Minimum weeks between deprecation notice and removal (default: 12) +min-deprecation-weeks=12 + +[CIFMW-C1] +# CI Framework deprecation compliance violations +# This rule is automatically triggered by CIFMW-CR1 when issues are found diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91bc52686b..b3e72ce697 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,5 +43,13 @@ repos: hooks: - id: ansible-lint additional_dependencies: + - ansible-core==2.15.13 - netaddr - jmespath + + # Conventional commits validation using gitlint (mature Python solution) + - repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint + stages: [commit-msg] diff --git a/RELEASE_CADENCE.md b/RELEASE_CADENCE.md new file mode 100644 index 0000000000..44d3a4d8a9 --- /dev/null +++ b/RELEASE_CADENCE.md @@ -0,0 +1,745 @@ +# CI Framework Release Cadence & Deprecation Process + +**Document Version:** 1.0 +**Last Updated:** September 22, 2025 +**Status:** Proposed +**Owner:** CI Framework Team + +## Executive Summary + +This document outlines the CI Framework's proposed release cadence and deprecation process, designed to provide stability for downstream consumers while maintaining development velocity. The solution implements a bi-weekly release cycle with structured deprecation timelines. + +## Table of Contents + +- [Background](#background) +- [Release Cadence Solution](#release-cadence-solution) +- [Deprecation Process](#deprecation-process) +- [Enforcement Strategy](#enforcement-strategy) +- [Implementation Plan](#implementation-plan) +- [Success Metrics](#success-metrics) +- [Risk Mitigation](#risk-mitigation) +- [Appendices](#appendices) + +## Background + +### Current Challenges + +Based on the CI tooling team discussion on September 17, 2025, the current workflow presents several challenges: + +#### Workflow and Stability Disruptions + +• **Daily Sync Instability**: The main branch currently syncs daily changes to the stable CI framework branch. This process causes frequent disruption and instability for users. Users complain about constant changes that distrupt their current workflows. + +• **Lack of Versioning**: A significant challenge is the lack of project versioning and the practice of pushing changes directly to the main branch. This makes it difficult for end users to easily understand changes or decide when to update their jobs. + +• **Predictability Issues**: The lack of a defined release cadence means there is no advance versioning or defined release time between main and stable branches. Consequently, users face unpredictable breaking changes. + +#### Deprecation and Development Problems + +• **Issues with Deprecation**: The current process creates problems when trying to deprecate and remove playbooks from the CI framework. Confusion was expressed regarding how a new release cadence would even assist with playbook deprecation. + +• **Long-Term Project Submission**: The existing structure causes issues when submitting Pull Requests (PRs) for long-term development changes. + +• **Conflicting Goals ("Chicken and Egg Problem")**: There is a "chicken and egg problem" of wanting to release breaking changes while simultaneously keeping customers undisrupted. This is exacerbated by the difficulty downstream teams have with long-term planning because they cannot rely on stable component availability. + +#### User Compliance and Implementation Concerns + +• **Ensuring Updates**: A challenge lies in ensuring all downstream CI jobs update to the designated stable branch or new tags. + +• **Communication Gaps**: Breaking changes surprise downstream consumers because of communication gaps and the lack of advance notice for deprecated components. + +• **Overhead Concerns**: Concerns were raised regarding the potential overhead and complexity of implementing a new structured system, such as a tagging system. + +The proposed solution to these issues involves adopting a stable branch and tags approach, with a predictable bi-weekly release cadence. + +### Team Consensus + +The team agreed on the following principles: +- **Stable branch + tagging approach** over complex branching strategies +- **2-week release cycle** for predictable cadence with CalVer versioning +- **6-release minimum deprecation timeline** (12 weeks notice) +- **Automation-first enforcement** to reduce manual overhead + +## Release Cadence Solution + +### Overview + +``` +Development Flow: +main branch (development) → stable branch (auto-promoted when criteria met) → tagged releases (scheduled) +``` + +### Current State +- **Stable branch automation**: Already exists - automatically updates when validation criteria are met +- **Missing component**: Scheduled tagging process for predictable releases +- **Goal**: Implement bi-weekly tagging of stable branch for consumption + +### Release Schedule + +- **Frequency**: Every 2 weeks (bi-weekly) +- **Release Cycle**: 2 weeks +- **Release Day**: Tuesday of even-numbered weeks +- **Emergency Releases**: Hot-fix tags as needed + +### Versioning Scheme + +**Format:** `YYYY.WW.PATCH` (CalVer - Calendar Versioning) + +- **YYYY**: Year +- **WW**: ISO week number (01-53 annually) +- **PATCH**: Hot-fix increment + +**Examples:** +- `2025.42.0` - Week 42, 2025 release +- `2025.42.1` - Hot-fix for Week 42 release +- `2025.44.0` - Next release (2 weeks later) + +### Release Process + +#### Week 1: Active Development +- All development on `main` branch +- Continuous integration testing +- Feature development and bug fixes +- Stable branch automatically updates when criteria are met + +#### Week 2: Release Tagging +- **Monday-Wednesday**: Continue development, the stable branch continues its auto-updates +- **Monday**: Evaluate current stable branch commit for release tagging +- **Tuesday**: Create release tag from stable branch, send release communication + +#### Stable Branch Automation (Ongoing) +- **Automatic promotion**: Main → stable when validation criteria are met +- **No manual intervention**: Process runs continuously based on CI results +- **Release readiness**: Stable branch is always in a releasable state + +### Tagging Process (To Be Implemented) + +#### Release Tag Creation +```bash +# Automated script for bi-weekly releases +./scripts/create-release-tag + +# Dry run to see what would happen +./scripts/create-release-tag --dry-run + +# Force tagging even if stable branch is stale +FORCE=true ./scripts/create-release-tag +``` + +#### Tag Selection Criteria +- **Source**: Current HEAD of stable branch (automatically promoted) +- **Timing**: Every Tuesday at 15:00 UTC via automation +- **Validation**: + - Verify stable branch has recent commits (within last 2 weeks) + - Ensure stable branch passed all promotion criteria + - Ensure there are no duplicate tags +- **Naming**: Follow CalVer format `YYYY.WW.PATCH` + +#### Hot-fix Tags +- **Trigger**: Critical issues in current release +- **Process**: Cherry-pick fix to stable, create patch release +- **Example**: `2025.42.1` for hot-fix of `2025.42.0` +- **Automation**: Patch increment handled by `create-release-tag` script + +### Branch Strategy + +| Branch | Purpose | Update Frequency | Consumer Usage | +|--------|---------|-----------------|----------------| +| `main` | Active development | Continuous | CI Framework developers only | +| `stable` | Tested, ready for consumption | Automatic (when criteria met) | Available for consumption | +| Tags | Fixed release points | Every 2 weeks (Tuesday) | Required for production jobs | + +## Deprecation Process + +### Timeline Overview + +**Total Timeline**: A minimum of 6 releases (12 weeks) + +| Phase | Release | Timeline | Actions Required | +|-------|---------|----------|------------------| +| **Initial Notice** | Release N | Week 0 | Deprecation announcement, runtime warnings | +| **Reinforcement** | Release N+2 | Week 4 | Second notice, migration support | +| **Final Warning** | Release N+4 | Week 8 | Mandatory acknowledgment, legacy tag info | +| **Removal** | Release N+6 | Week 12 | Component removed from main branch | + +### Phase 1: Initial Deprecation Notice (Week 0) + +**Actions:** +- Email notification to all stakeholders +- Add runtime deprecation warnings to component +- Create migration documentation +- Update release notes with deprecation notice + +**Required Information:** +- Component name and location +- Deprecation reason +- Migration path and alternative +- Removal timeline (specific release) + +### Phase 2: Deprecation Reinforcement (Week 4) + +**Actions:** +- Second email reminder to stakeholders +- Enhanced warning messages in component logs +- Migration support office hours announced +- Jira tickets created for affected teams + +### Phase 3: Final Warning (Week 8) + +**Actions:** +- Final removal notice email +- List of tags that will retain deprecated features +- Emergency contact information provided +- Mandatory team acknowledgment required + +### Phase 4: Removal (Week 12) + +**Actions:** +- Remove component from main branch +- Update all documentation +- Create legacy support tag references +- Monitor for post-removal issues + +### Deprecation Notice Format + +```yaml +# DEPRECATED: component_name +# Will be removed in release 2025.48.0 +# Reason: Replaced by more efficient implementation +# Migration: Use new_component_name instead +# See: docs/migration/component_name.md + +- name: Show deprecation warning + debug: + msg: | + ⚠️ DEPRECATION WARNING ⚠️ + + Component: {{ component_name }} + Status: Will be removed in {{ removal_release }} + + 🔄 MIGRATION REQUIRED: + Replace with: {{ alternative }} + Guide: docs/migration/{{ component_name }}.md + + 📅 Timeline: + - Now: Deprecated, warnings shown + - {{ removal_release }}: Component removed + - Legacy access: Available in current and earlier tags + + 👥 Need help? Contact @ci-framework-team + when: + - cifmw_deprecation_warnings | default(true) | bool + tags: + - always + - deprecation-warning +``` + +### Communication Templates + +#### Stakeholder Notification Email +``` +Subject: [CI Framework] Deprecation Notice - [COMPONENT] removal in 12 weeks + +Dear Teams, + +We are deprecating [COMPONENT] in CI Framework release [REMOVAL_RELEASE] +(scheduled for [DATE]). + +Affected Components: +- [LIST OF COMPONENTS] + +Migration Required: +- Replace with: [ALTERNATIVE] +- Migration guide: [DOCUMENTATION_LINK] +- Example changes: [EXAMPLE_LINK] + +Timeline: +- Deprecation Notice: Today +- Final Removal: [REMOVAL_RELEASE] release (12 weeks) +- Last Supporting Tag: [CURRENT_RELEASE] + +Action Required: +1. Review your jobs using deprecated components +2. Plan migration during next 6 weeks +3. Test with new components +4. Contact us for migration assistance + +Questions? Reply to this email or contact the CI Framework team. +``` + +#### Release Announcement Template +``` +Subject: [CI Framework] Release 2025.42.0 Available + +Bi-weekly Release: 2025.42.0 (Week 42, 2025) +Previous Release: 2025.40.0 (2 weeks ago) + +Release Highlights: +- New features: X, Y +- Bug fixes: A, B, C +- Deprecation warnings: playbook XYZ (removal in 2025.48.0) + +Breaking Changes: None this release +Migration Required: None this release + +Next Release: 2025.44.0 (in 2 weeks) +Legacy Support: 2025.34.0 through 2025.40.0 still supported + +Usage Instructions: +- Update your job tags to: 2025.42.0 +- Emergency rollback tags: 2025.40.0, 2025.38.0 +``` + +## Enforcement Strategy + +### Technical Enforcement + +#### 1. Commit Validation + +**Mature Python-Based Tools from [conventionalcommits.org](https://www.conventionalcommits.org/en/about/#tooling-for-conventional-commits):** +- **Gitlint**: Python-based conventional commits linter + - Install: `pip install -r test-requirements.txt` (includes gitlint==0.19.1) + - Config: `.gitlint` configuration file included + - Usage: Automatic via pre-commit hooks + +- **Commitizen**: Python conventional commits workflow tool + - Install: `pip install -r test-requirements.txt` (includes commitizen==3.29.0) + - Usage: `cz commit` for interactive commit creation + - Supports: Auto-changelog generation and version bumping + +- **Pre-commit**: Essential for automated validation + - Install: `pip install pre-commit && pre-commit install --hook-type commit-msg` + - Benefits: Battle-tested, community maintained, comprehensive features + +**CI Framework Integration:** +- **Conventional Commits**: Handled entirely by gitlint via pre-commit hooks +- **Deprecation Compliance**: `scripts/check-deprecation.py` (CI Framework specific rules only) +- **Pre-commit Config**: `.pre-commit-config.yaml` (clean separation: gitlint + CI Framework specific tools) + +#### 2. CI/CD Pipeline Gates +- **GitHub Actions**: Perform deprecation compliance checks on PRs +- **Zuul Integration**: Automated validation for OpenStack CI +- **Failure Conditions**: Will fail if missing migration docs or the timeline is insufficient + +#### 3. Developer Tools + +##### Deprecation Helper CLI +```bash +# Interactive deprecation workflow +./scripts/cifmw-deprecate --interactive + +# Non-interactive usage +./scripts/cifmw-deprecate --component "playbooks/old.yml" \ + --type playbook --reason "Performance issues" \ + --alternative "playbooks/new.yml" +``` + +##### Status Dashboard +```bash +# View current deprecation status +./scripts/deprecation-status.py + +# Export JSON report +./scripts/deprecation-status.py --export report.json +``` + +##### Conventional Commits Validation (Recommended Tools) +```bash +# Using Pre-commit (Recommended) +pip install pre-commit +pre-commit install + +# Using Gitlint +pip install -r test-requirements.txt # Includes gitlint + commitizen +pre-commit install --hook-type commit-msg + +# Validate commit messages +gitlint --msg-filename path/to/commit-message.txt +gitlint lint --commits HEAD~5..HEAD # Validate commit range + +# Interactive commit creation with Commitizen +cz commit +``` + +### Process Enforcement + +#### 1. Pull Request Templates +- Mandatory checklists for breaking changes +- Deprecation compliance verification +- Migration documentation requirements + +#### 2. Code Review Standards +- Minimum 2 reviewers for deprecations +- Team lead approval for major changes +- Mandatory migration guide review + +#### 3. Automated Monitoring +- Weekly compliance reports +- Slack/Teams alerts for violations +- Compliance threshold enforcement (85% minimum) + +### Developer Experience + +#### 1. Developer Setup & VS Code Integration + +**Setup (Python only, no npm required):** +```bash +# Install and setup pre-commit (uses existing .pre-commit-config.yaml) +pip install pre-commit +pre-commit install + +# Test validation +git commit -m "feat: add user authentication" +``` + +**VS Code Integration:** +- **Conventional Commit Snippets**: `.vscode/conventional-commits.code-snippets` + - `ccfeat` - Feature commits + - `ccfix` - Bug fix commits + - `ccdeprecate` - Deprecation commits + - `ccremove` - Removal commits + - `depwarn` - Deprecation warnings for YAML files +- **Auto-completion**: Migration guide paths, component references + +#### 2. Commit Validation Rules & Examples + +**Conventional Commits Standard:** +- **Format**: `[optional scope]: ` +- **Types**: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert +- **Breaking changes**: Use `!` or `BREAKING CHANGE:` footer +- **Length**: Subject ≤ 72 chars, body lines ≤ 100 chars + +**CI Framework Extensions:** +- **Deprecation commits** must reference migration docs +- **Removal commits** should specify target version (CalVer format) +- **Ansible changes** should include component paths +- **Breaking changes** need detailed descriptions + +**Examples:** +```bash +# ✅ Good conventional commits +feat: add user authentication +fix(roles): correct variable name in deploy_bmh +feat!: deprecate legacy playbook xyz + +BREAKING CHANGE: legacy playbook xyz removed, use abc instead +See: docs/migration/playbook-xyz.md + +# ❌ Bad commits (will be rejected) +add some stuff +WIP: testing things +Update files +``` + +**Troubleshooting:** +```bash +# Fix formatting issues +echo "your message" | npx commitlint + +# Generate proper templates +./scripts/cifmw-deprecate --interactive + +# Skip validation (emergency only) +git commit --no-verify -m "hotfix: critical security patch" +``` + +#### 3. Documentation Templates +- Migration guide generators +- Deprecation notice formats +- Communication templates + +## Implementation Plan + +### Phase 1: Foundation Setup (Weeks 1-2) + +**Week 1:** +- [ ] Deploy technical enforcement tools + - `scripts/check-deprecation.py` + - Pre-commit hooks configuration + - CI/CD pipeline integration +- [ ] Create pull request templates +- [ ] Design automated tagging process + +**Week 2:** +- [ ] Deploy developer CLI tools + - `scripts/cifmw-deprecate` + - VS Code snippets and templates + - Documentation templates +- [ ] Implement automated tagging workflow +- [ ] Test tag creation and release process + +### Phase 2: Process Integration (Weeks 3-4) + +**Week 1:** +- [ ] Launch deprecation status dashboard +- [ ] Setup automated compliance monitoring + +**Week 2:** +- [ ] Integrate with Jira for tracking +- [ ] Establish team review standards +- [ ] Test complete enforcement pipeline + +### Phase 3: Team Adoption (Weeks 5-6) + +**Week 1:** +- [ ] Establish compliance metrics (85% threshold) +- [ ] Launch peer review requirements + +**Week 2:** +- [ ] Deploy notification integrations +- [ ] Create knowledge base documentation +- [ ] Conduct first compliance audit + +### Phase 4: First Production Release (Weeks 7-8) + +**Week 1:** +- [ ] Migrate consuming teams to tagged releases +- [ ] Validate job compatibility +- [ ] Monitor migration success + +**Week 2:** +- [ ] Execute first official 2-week release +- [ ] Send stakeholder communications +- [ ] Monitor and gather feedback + +### Phase 5: Optimization (Ongoing) + +- [ ] Monitor compliance metrics +- [ ] Refine tools based on feedback +- [ ] Quarterly process reviews + +## Success Metrics + +### Release Cadence Metrics + +| Metric | Target | Measurement | +|--------|--------|-------------| +| On-time releases | 99% | Every Tuesday bi-weekly release | +| Release window | <2 hours | Monday validation to Tuesday tag | +| Zero surprise breakages | 100% | All breaking changes have 12-week notice | +| Quick adoption | 90% | Teams update to new tags within 2 weeks | + +### Deprecation Process Metrics + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Advance notice compliance | 100% | All deprecations provide 12+ week notice | +| Migration guide coverage | 100% | Every deprecated item has replacement docs | +| Support escalations | <3 per deprecation | Teams can self-migrate with guides | +| Emergency rollbacks | 0 | No surprise removals requiring urgent fixes | + +### Enforcement Effectiveness + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Compliance rate | >85% | Automated deprecation process compliance | +| Tool usage | >90% | Deprecations using `cifmw-deprecate` helper | +| False positive rate | <5% | Incorrect automated compliance flags | + +### Developer Experience + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Review efficiency | <2 days | Average deprecation PR review time | +| Tool satisfaction | >85% | Developer satisfaction with enforcement tools | +| Support tickets | <3 per deprecation | Migration assistance requests | + +## Risk Mitigation + +### High-Frequency Release Risks + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Quality degradation due to speed | High | Automated quality gates, mandatory Monday validation | +| Team fatigue from bi-weekly releases | Medium | Full automation, minimal manual intervention | +| Insufficient testing time | High | Continuous testing on main, Monday gate validation | +| Communication overload | Medium | Automated announcements, digest format, opt-in notifications | + +### Deprecation Process Risks + +| Risk | Impact | Mitigation | +|------|--------|------------| +| 12 weeks too short for complex migrations | High | Extend to 18 weeks for major architectural changes | +| Teams miss notices in bi-weekly flow | Medium | Multi-channel notifications, mandatory acknowledgment | +| Tool maintenance overhead | Medium | Simple technologies, extensive test coverage | +| Process drift over time | Medium | Quarterly reviews, continuous monitoring | + +### Enforcement Risks + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Developer resistance to process | Medium | Excellent tooling, automation over manual work | +| Over-reliance on automation | Medium | Human review for major changes, regular audits | +| False security from tools | Low | Community feedback, effectiveness reviews | + +## Appendices + +### Appendix A: Tool Documentation + +#### check-deprecation.py +- **Purpose**: Pre-commit validation of deprecation compliance +- **Usage**: Integrated with git hooks and CI pipelines +- **Validations**: Conventional commits, timeline compliance, documentation + +#### cifmw-deprecate +- **Purpose**: Interactive deprecation workflow helper +- **Usage**: `./scripts/cifmw-deprecate --interactive` +- **Features**: Template generation, timeline calculation, documentation creation + +#### deprecation-status.py +- **Purpose**: Compliance monitoring and reporting dashboard +- **Usage**: `./scripts/deprecation-status.py --export report.json` +- **Features**: Status tracking, compliance metrics, automated alerts + +#### create-release-tag +- **Purpose**: Automated bi-weekly release tagging from stable branch +- **Usage**: `./scripts/create-release-tag [--dry-run] [--force]` +- **Features**: CalVer tagging, activity validation, release announcements + +#### check-deprecation.py +- **Purpose**: CI Framework specific deprecation compliance validation +- **Usage**: `./scripts/check-deprecation.py --commit-msg "message"` or `--files file1 file2` +- **Features**: CalVer format validation, deprecation timeline checks, migration documentation validation +- **Note**: Conventional commits validation handled separately by gitlint + +#### Mature Tools from Conventional Commits Ecosystem +- **Gitlint**: `pip install -r test-requirements.txt` (Python-based conventional commits linter) +- **Commitizen**: `pip install -r test-requirements.txt` (Interactive commit tool with auto-changelog) +- **Pre-commit**: `pip install pre-commit && pre-commit install --hook-type commit-msg` (Hook framework) + +### Appendix B: Integration Examples + +#### Current Pre-commit Configuration +```yaml +# .pre-commit-config.yaml (CI Framework setup with conventional commits) +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + - id: check-executables-have-shebangs + - id: check-merge-conflict + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + + - repo: https://github.com/ansible/ansible-lint + rev: v6.22.2 + hooks: + - id: ansible-lint + + # Conventional commits validation using gitlint (mature Python solution) + - repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint + stages: [commit-msg] +``` + + +#### GitHub Actions Workflow +```yaml +# .github/workflows/commit-compliance.yml +name: Commit & Deprecation Compliance Check +on: + pull_request: + branches: [main, stable] +jobs: + compliance-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Need full history for commit validation + + - name: Validate Conventional Commits + run: | + # Install and validate using commitlint (standard tool) + npm install -g @commitlint/cli @commitlint/config-conventional + npx commitlint --from origin/main --to HEAD --verbose + + - name: Check Deprecation Compliance + run: | + # Check changed files for deprecation compliance + CHANGED_FILES=$(git diff --name-only origin/main..HEAD) + if [ -n "$CHANGED_FILES" ]; then + python scripts/check-deprecation.py --files $CHANGED_FILES + fi + + - name: Generate Compliance Report + if: failure() + run: | + echo "## Compliance Check Failed" >> $GITHUB_STEP_SUMMARY + echo "Please ensure your commits follow conventional commit format" >> $GITHUB_STEP_SUMMARY + echo "and deprecations include proper documentation." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Quick Fixes:" >> $GITHUB_STEP_SUMMARY + echo "- Setup validation: \`pip install pre-commit && pre-commit install\`" >> $GITHUB_STEP_SUMMARY + echo "- Setup guide: See Developer Experience section in this document" >> $GITHUB_STEP_SUMMARY + echo "- Deprecation helper: \`./scripts/cifmw-deprecate --interactive\`" >> $GITHUB_STEP_SUMMARY +``` + +### Appendix C: Training Materials + +#### Training Modules + +1. **Understanding Deprecation Impact** + - Why proper deprecation matters for CI stability + - Cost of breaking changes to downstream consumers + - CI Framework's commitment to predictable releases + +2. **Technical Implementation** + - How to use the `cifmw-deprecate` helper tool + - Writing proper conventional commits + - Creating migration documentation + +3. **Process Compliance** + - 6-release minimum timeline + - Communication requirements + - Code review standards + +### Appendix D: Communication Standards + +#### Team Standards +```yaml +deprecation_standards: + mandatory_practices: + - conventional_commits: "All breaking changes use conventional commit format" + - migration_docs: "Every deprecation includes migration guide" + - runtime_warnings: "Components show deprecation warnings during execution" + - stakeholder_notification: "Email notifications sent to all affected teams" + - minimum_timeline: "6 releases minimum between deprecation and removal" + + recommended_practices: + - advance_communication: "Discuss major deprecations in team meetings" + - user_support: "Offer migration assistance during deprecation period" + - gradual_removal: "Phase out complex components over multiple releases" + - testing_support: "Provide test environments for migration validation" + + forbidden_practices: + - silent_removal: "Never remove components without proper deprecation" + - immediate_breaking: "No same-release breaking changes" + - insufficient_notice: "Less than 6 releases deprecation period" + - missing_alternatives: "Deprecating without providing migration path" +``` + +--- + +**Document Status**: This plan requires team approval and stakeholder sign-off before implementation. + +**Next Steps**: +1. Team review and feedback collection +2. Stakeholder notification and approval +3. Implementation timeline finalization +4. Tool deployment and testing + +**Contact**: CI Framework Team for questions or feedback on this plan. diff --git a/gitlint_rules/README.md b/gitlint_rules/README.md new file mode 100644 index 0000000000..eb942a6e6e --- /dev/null +++ b/gitlint_rules/README.md @@ -0,0 +1,80 @@ +# CI Framework Custom Gitlint Rules + +This directory contains custom gitlint rules for enforcing CI Framework specific deprecation compliance. + +## Overview + +Deprecation validation is integrated directly into gitlint using [Configuration Rules](https://jorisroovers.com/gitlint/latest/rules/user_defined_rules/configuration_rules/). This provides: + +- **Integrated workflow**: Part of existing gitlint validation in pre-commit +- **Dynamic behavior**: Rules can modify gitlint behavior based on commit content +- **Better error reporting**: Leverages gitlint's violation system +- **Cleaner architecture**: No separate tool to maintain + +## Rules + +### DeprecationConfigurationRule (CIFMW-CR1) +- **Type**: Configuration Rule (runs before other rules) +- **Purpose**: Detects deprecation-related commits and configures validation behavior +- **Features**: + - Detects commits with deprecation keywords (`deprecate`, `remove`, `delete`, etc.) + - Validates migration documentation references in commit messages + - Checks staged files for proper deprecation format (CalVer) + - Validates deprecation timeline (minimum 12 weeks by default) + - Adjusts body line length to 120 chars for deprecation commits + +### DeprecationCommitRule (CIFMW-C1) +- **Type**: Commit Rule (validates based on configuration rule findings) +- **Purpose**: Reports specific deprecation compliance violations +- **Violations**: + - Missing migration documentation references + - Improper deprecation format in files + - Insufficient deprecation timeline + +## Configuration + +The rules support configuration options in `.gitlint`: + +```ini +[CIFMW-CR1] +# Minimum weeks between deprecation notice and removal (default: 12) +min-deprecation-weeks=12 +``` + +## Expected Deprecation Format + +### Commit Messages +Deprecation commits should reference migration documentation: +``` +refactor: deprecate old authentication module + +This removes the deprecated auth module. See docs/migration/auth-migration.md +for alternative components. + +Signed-off-by: Developer +``` + +### File Format (YAML/Ansible) +```yaml +# DEPRECATED: Will be removed in release 2025.52.0 +# Migration: Use new_auth_module instead, see docs/migration/auth-migration.md +- name: Old auth task +``` + +## Testing + +Test the rules with various commit messages: + +```bash +# Valid deprecation commit +echo "refactor: deprecate auth module + +See docs/migration/auth.md for alternatives. + +Signed-off-by: Dev " | gitlint --msg-filename /dev/stdin + +# Invalid (missing migration reference) +echo "refactor: remove old module + +No migration info provided." | gitlint --msg-filename /dev/stdin +``` diff --git a/gitlint_rules/deprecation_rules.py b/gitlint_rules/deprecation_rules.py new file mode 100644 index 0000000000..1f7bcca9f2 --- /dev/null +++ b/gitlint_rules/deprecation_rules.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +CI Framework Deprecation Configuration Rule for Gitlint +Enforces deprecation compliance rules integrated with gitlint workflow +""" + +import os +import re +import subprocess +from datetime import datetime +from pathlib import Path +from typing import List + +from gitlint.options import IntOption +from gitlint.rules import ConfigurationRule, CommitRule, RuleViolation + + +class DeprecationConfigurationRule(ConfigurationRule): + """ + Configuration rule that enforces CI Framework deprecation compliance. + Applied before other gitlint rules to validate deprecation practices. + """ + + name = "ci-framework-deprecation-config" + id = "CIFMW-CR1" # CI Framework Configuration Rule 1 + + options_spec = [ + IntOption("min-deprecation-weeks", 12, "Minimum weeks between deprecation notice and removal") + ] + + def apply(self, config, commit): + """Apply deprecation configuration rules to the commit""" + self.log.debug("Checking CI Framework deprecation compliance") + + # Get current release for timeline validation + current_release = self._get_current_release() + + # Check commit message for deprecation-related content + commit_msg = commit.message.full + + if self._is_deprecation_commit(commit_msg): + self.log.debug("Found deprecation-related commit") + + # Validate migration documentation references + if not self._has_migration_reference(commit_msg): + # Add custom property to trigger specific rule later + commit.deprecation_missing_migration = True + self.log.debug("Deprecation commit missing migration reference") + + # For deprecation commits, be more lenient on line length + # since they might need longer explanations + config.set_rule_option("body-max-line-length", "line-length", 120) + + # Check staged files for proper deprecation format + staged_files = self._get_staged_files() + for file_path in staged_files: + if file_path.endswith(('.yml', '.yaml')): + issues = self._check_file_deprecation_format(file_path, current_release) + # Store issues on commit for later rule processing + if not hasattr(commit, 'deprecation_file_issues'): + commit.deprecation_file_issues = [] + commit.deprecation_file_issues.extend(issues) + + def _get_current_release(self) -> str: + """Get current release from git tags or generate from date""" + try: + result = subprocess.run( + ['git', 'describe', '--tags', '--abbrev=0'], + capture_output=True, text=True, check=True + ) + return result.stdout.strip() + except: + # Fallback: generate CalVer based on current date + now = datetime.now() + week = now.isocalendar()[1] + return f"{now.year}.{week:02d}.0" + + def _is_deprecation_commit(self, commit_msg: str) -> bool: + """Check if commit is deprecation-related""" + # Be more specific to avoid false positives + deprecation_keywords = ['deprecat', 'obsolete', 'sunset'] + removal_keywords = ['remove.*deprecated', 'delete.*deprecated', 'drop.*deprecated'] + + msg_lower = commit_msg.lower() + + # Check explicit deprecation keywords + if any(keyword in msg_lower for keyword in deprecation_keywords): + return True + + # Check removal of deprecated items (more specific) + return any(re.search(pattern, msg_lower) for pattern in removal_keywords) + + def _has_migration_reference(self, commit_msg: str) -> bool: + """Check if commit references migration documentation""" + migration_patterns = [ + r'docs/migration/', + r'migration.*guide', + r'see.*docs', + r'alternative.*component', + r'replaced.*by', + r'use.*instead' + ] + return any( + re.search(pattern, commit_msg, re.IGNORECASE) + for pattern in migration_patterns + ) + + def _get_staged_files(self) -> List[str]: + """Get list of staged files from git""" + try: + result = subprocess.run( + ['git', 'diff', '--cached', '--name-only'], + capture_output=True, text=True, check=True + ) + return result.stdout.strip().split('\n') if result.stdout.strip() else [] + except subprocess.CalledProcessError: + return [] + + def _check_file_deprecation_format(self, file_path: str, current_release: str) -> List[dict]: + """Check file for proper deprecation format""" + issues = [] + + if not os.path.exists(file_path): + return issues + + try: + with open(file_path, 'r') as f: + content = f.read() + + lines = content.split('\n') + for i, line in enumerate(lines, 1): + if 'deprecat' in line.lower(): + # Check for proper CalVer format + pattern = r'# DEPRECATED.*?(\d{4}\.\d{2}\.\d+)' + match = re.search(pattern, line, re.IGNORECASE) + + if not match: + issues.append({ + 'file': file_path, + 'line': i, + 'type': 'improper_format', + 'message': f"Deprecation notice should follow CalVer format: '# DEPRECATED: Will be removed in release YYYY.WW.PATCH'" + }) + else: + # Validate timeline if removal version is specified + removal_version = match.group(1) + timeline_issue = self._validate_deprecation_timeline( + current_release, removal_version + ) + if timeline_issue: + timeline_issue['file'] = file_path + timeline_issue['line'] = i + issues.append(timeline_issue) + + except Exception as e: + issues.append({ + 'file': file_path, + 'line': 0, + 'type': 'read_error', + 'message': f"Could not read file: {e}" + }) + + return issues + + def _validate_deprecation_timeline(self, current_release: str, removal_release: str) -> dict: + """Validate deprecation timeline meets minimum requirements""" + try: + # Parse CalVer format YYYY.WW.PATCH + current_match = re.match(r'(\d{4})\.(\d{2})\.(\d+)', current_release) + removal_match = re.match(r'(\d{4})\.(\d{2})\.(\d+)', removal_release) + + if not current_match or not removal_match: + return { + 'type': 'invalid_format', + 'message': "Release versions should follow CalVer YYYY.WW.PATCH format" + } + + current_year = int(current_match.group(1)) + current_week = int(current_match.group(2)) + removal_year = int(removal_match.group(1)) + removal_week = int(removal_match.group(2)) + + # Calculate weeks difference + current_total_weeks = current_year * 52 + current_week + removal_total_weeks = removal_year * 52 + removal_week + weeks_difference = removal_total_weeks - current_total_weeks + + min_weeks = self.options["min-deprecation-weeks"].value + + if weeks_difference < min_weeks: + return { + 'type': 'insufficient_timeline', + 'message': f"Deprecation period too short: {weeks_difference} weeks. Minimum {min_weeks} weeks required." + } + + except Exception as e: + return { + 'type': 'timeline_error', + 'message': f"Could not validate timeline: {e}" + } + + return None + + +class DeprecationCommitRule(CommitRule): + """ + Commit rule that validates deprecation compliance based on + data gathered by DeprecationConfigurationRule + """ + + name = "ci-framework-deprecation-violations" + id = "CIFMW-C1" # CI Framework Commit rule 1 + + def validate(self, commit): + """Validate commit for deprecation compliance violations""" + violations = [] + + # Check for missing migration reference in deprecation commits + if hasattr(commit, 'deprecation_missing_migration') and commit.deprecation_missing_migration: + violations.append(RuleViolation( + self.id, + "Deprecation commit should reference migration documentation or alternative component", + line_nr=1 + )) + + # Report file-level deprecation format issues + if hasattr(commit, 'deprecation_file_issues'): + for issue in commit.deprecation_file_issues: + violations.append(RuleViolation( + self.id, + f"{issue['file']}:{issue['line']} - {issue['message']}", + line_nr=1 + )) + + return violations diff --git a/scripts/cifmw-deprecate b/scripts/cifmw-deprecate new file mode 100755 index 0000000000..28d6ef50cd --- /dev/null +++ b/scripts/cifmw-deprecate @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +""" +CI Framework Deprecation Helper Tool +Makes it easy for developers to properly deprecate components +""" + +import os +import sys +import argparse +import subprocess +from pathlib import Path +from datetime import datetime +from textwrap import dedent + + +class DeprecationHelper: + def __init__(self): + self.current_release = self._get_current_release() + + def _get_current_release(self): + """Get current release from git tags""" + try: + result = subprocess.run( + ['git', 'describe', '--tags', '--abbrev=0'], + capture_output=True, text=True, check=True + ) + return result.stdout.strip() + except: + # Fallback to date-based release + now = datetime.now() + week = now.isocalendar()[1] + return f"{now.year}.{week:02d}.0" + + def _calculate_removal_release(self, current_release, releases_ahead=6): + """Calculate removal release (6 releases minimum)""" + # Parse current release (YYYY.WW.PATCH) + parts = current_release.split('.') + year = int(parts[0]) + week = int(parts[1]) + + target_week = week + (releases_ahead * 2) # Bi-weekly releases + target_year = year + + # Handle year rollover (52-53 weeks per year) + while target_week > 52: + target_year += 1 + target_week -= 52 + + return f"{target_year}.{target_week:02d}.0" + + def create_deprecation_notice(self, component_type, component_name, reason, alternative=None): + """Create properly formatted deprecation notice""" + removal_release = self._calculate_removal_release(self.current_release) + + notice = dedent(f""" + # DEPRECATED: {component_name} + # Will be removed in release {removal_release} + # Reason: {reason} + """).strip() + + if alternative: + notice += f"\n# Migration: Use {alternative} instead" + + notice += f"\n# See: docs/migration/{component_name.replace('/', '_')}.md" + + return notice, removal_release + + def create_migration_doc(self, component_name, component_type, alternative, removal_release): + """Generate migration documentation template""" + doc_name = component_name.replace('/', '_') + doc_path = f"docs/migration/{doc_name}.md" + + content = dedent(f""" + # Migration Guide: {component_name} + + ## Deprecation Notice + + **Component:** `{component_name}` + **Type:** {component_type} + **Deprecated in:** {self.current_release} + **Removal target:** {removal_release} + **Status:** ⚠️ Deprecated + + ## Migration Required + + ### Old Usage (Deprecated) + ```yaml + # Example of old usage - will be removed! + - name: Old way + include: {component_name} + ``` + + ### New Usage (Recommended) + ```yaml + # New recommended approach + - name: New way + include: {alternative if alternative else 'NEW_COMPONENT_HERE'} + ``` + + ## Migration Steps + + 1. **Identify usage** in your job definitions + 2. **Replace component reference** with new alternative + 3. **Update any component-specific parameters** + 4. **Test your job** with the new component + 5. **Remove deprecated component** from your job + + ## Key Differences + + | Deprecated Component | New Component | Notes | + |---------------------|---------------|-------| + | `{component_name}` | `{alternative or 'TBD'}` | TODO: Document differences | + + ## Timeline + + - **{self.current_release}**: Deprecation announced, warnings added + - **{removal_release}**: Component removed from main branch + - **Legacy access**: Available in tags {self.current_release} and earlier + + ## Support + + - **Documentation**: [CI Framework Docs](../README.md) + - **Migration Help**: Contact @ci-framework-team + - **Issues**: Create ticket in [CIFMW Jira](https://issues.redhat.com/projects/CIFMW) + """).strip() + + return doc_path, content + + def create_conventional_commit_template(self, component_name, action="deprecate"): + """Generate proper conventional commit message""" + if action == "deprecate": + return dedent(f""" + feat!: deprecate {component_name} + + BREAKING CHANGE: {component_name} is deprecated and will be removed + in 6 releases. Use [ALTERNATIVE] instead. + + - Added runtime deprecation warnings + - Created migration guide: docs/migration/{component_name.replace('/', '_')}.md + - Scheduled removal: 6 releases from now + + Refs: CIFMW-XXXX + """).strip() + elif action == "remove": + return dedent(f""" + feat!: remove deprecated {component_name} + + BREAKING CHANGE: {component_name} has been removed as scheduled. + Use [ALTERNATIVE] instead. + + - Component deprecated 6 releases ago + - Migration guide available: docs/migration/{component_name.replace('/', '_')}.md + - Legacy access via previous release tags + + Refs: CIFMW-XXXX + """).strip() + + def interactive_deprecation(self): + """Interactive deprecation workflow""" + print("🔧 CI Framework Deprecation Helper") + print("=====================================\n") + + # Gather information + print("📝 Component Information:") + component_type = input("Component type (playbook/role/task): ").strip() + component_name = input("Component name/path: ").strip() + reason = input("Deprecation reason: ").strip() + alternative = input("Alternative component (optional): ").strip() or None + + print(f"\n📅 Timeline (current release: {self.current_release}):") + removal_release = self._calculate_removal_release(self.current_release) + print(f" Removal target: {removal_release} (6 releases ahead)") + + # Generate content + notice, _ = self.create_deprecation_notice(component_type, component_name, reason, alternative) + doc_path, doc_content = self.create_migration_doc(component_name, component_type, alternative, removal_release) + commit_msg = self.create_conventional_commit_template(component_name) + + print("\n📋 Generated Content:") + print("====================") + + print("\n1️⃣ Deprecation Notice (add to component file):") + print("-" * 50) + print(notice) + + print(f"\n2️⃣ Migration Documentation ({doc_path}):") + print("-" * 50) + print(doc_content[:500] + "..." if len(doc_content) > 500 else doc_content) + + print("\n3️⃣ Commit Message Template:") + print("-" * 50) + print(commit_msg) + + # Offer to create files + if input("\n✍️ Create migration documentation file? (y/N): ").lower() == 'y': + Path("docs/migration").mkdir(parents=True, exist_ok=True) + with open(doc_path, 'w') as f: + f.write(doc_content) + print(f"✅ Created: {doc_path}") + + print("\n📋 Next Steps:") + print("1. Add deprecation notice to your component file") + print("2. Update your component to show runtime warnings") + print("3. Create PR with the generated commit message") + print("4. Send stakeholder notification email") + + return True + + +def main(): + parser = argparse.ArgumentParser(description='CI Framework Deprecation Helper') + parser.add_argument('--interactive', '-i', action='store_true', + help='Interactive deprecation workflow') + parser.add_argument('--component', help='Component to deprecate') + parser.add_argument('--type', choices=['playbook', 'role', 'task'], + help='Component type') + parser.add_argument('--reason', help='Deprecation reason') + parser.add_argument('--alternative', help='Alternative component') + + args = parser.parse_args() + + helper = DeprecationHelper() + + if args.interactive or not any([args.component, args.type, args.reason]): + helper.interactive_deprecation() + else: + # Non-interactive mode + notice, removal_release = helper.create_deprecation_notice( + args.type, args.component, args.reason, args.alternative + ) + print("Deprecation Notice:") + print(notice) + + doc_path, doc_content = helper.create_migration_doc( + args.component, args.type, args.alternative, removal_release + ) + print(f"\nMigration doc would be created at: {doc_path}") + + +if __name__ == '__main__': + main() diff --git a/scripts/create-release-tag b/scripts/create-release-tag new file mode 100755 index 0000000000..13d2acfbe4 --- /dev/null +++ b/scripts/create-release-tag @@ -0,0 +1,266 @@ +#!/usr/bin/env bash +""" +CI Framework Release Tagging Script +Creates bi-weekly release tags from the stable branch +""" + +set -e + +# Configuration +REPO_PATH="${REPO_PATH:-$(pwd)}" +STABLE_BRANCH="${STABLE_BRANCH:-stable}" +TAG_PREFIX="${TAG_PREFIX:-}" +DRY_RUN="${DRY_RUN:-false}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { + echo -e "${BLUE}[$(date +'%H:%M:%S')]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +# Calculate current week-based version +get_current_version() { + local year=$(date +%Y) + local week=$(date +%V) # ISO week number + echo "${year}.${week}.0" +} + +# Check if tag already exists +tag_exists() { + local tag="$1" + git tag -l | grep -q "^${tag}$" +} + +# Get the latest commit hash from stable branch +get_stable_commit() { + git rev-parse "${STABLE_BRANCH}" +} + +# Check if stable branch has recent activity +check_stable_activity() { + local days_since_last_commit + days_since_last_commit=$(git log -1 --format="%ct" "${STABLE_BRANCH}" | xargs -I {} date -d @{} +%s | xargs -I {} echo $(( ($(date +%s) - {}) / 86400 ))) + + if [ "$days_since_last_commit" -gt 14 ]; then + warn "Stable branch hasn't been updated in ${days_since_last_commit} days" + warn "Last commit: $(git log -1 --format="%h %s (%ar)" "${STABLE_BRANCH}")" + return 1 + fi + + log "Stable branch activity: last commit ${days_since_last_commit} days ago" + return 0 +} + +# Create release tag +create_tag() { + local version="$1" + local commit_hash="$2" + local tag_message="Release ${version} - Week $(date +%V), $(date +%Y) + +Created from stable branch commit: ${commit_hash} +Release date: $(date -u +"%Y-%m-%d %H:%M:%S UTC") + +This release includes all changes that have been automatically +promoted to the stable branch as of $(date -u +"%Y-%m-%d"). + +For detailed changes, see: +git log --oneline ${commit_hash}...$(git tag -l '20*' | tail -1) +" + + if [ "$DRY_RUN" = "true" ]; then + log "DRY RUN: Would create tag ${version} at commit ${commit_hash}" + log "Tag message:" + echo "$tag_message" + return 0 + fi + + # Create annotated tag + if git tag -a "$version" -m "$tag_message" "$commit_hash"; then + success "Created tag ${version} at commit ${commit_hash}" + + # Push tag to origin + if git push origin "$version"; then + success "Pushed tag ${version} to origin" + return 0 + else + error "Failed to push tag ${version}" + return 1 + fi + else + error "Failed to create tag ${version}" + return 1 + fi +} + +# Generate release announcement +generate_announcement() { + local version="$1" + local previous_version + previous_version=$(git tag -l '20*' | grep -v "$version" | sort -V | tail -1) + + local week_num=$(date +%V) + local year=$(date +%Y) + + cat << EOF +Subject: [CI Framework] Release ${version} Available + +Bi-weekly Release: ${version} (Week ${week_num}, ${year}) +Previous Release: ${previous_version} (2 weeks ago) + +Release Highlights: +$(git log --oneline "${previous_version}..stable" --pretty="- %s" | head -10) + +Breaking Changes: Check deprecation notices below +Migration Required: See individual component notices + +Next Release: Scheduled for $(date -d '+2 weeks' '+%Y.%V').0 (in 2 weeks) +Legacy Support: Recent releases remain supported + +Usage Instructions: +- Update your job tags to: ${version} +- Emergency rollback tags: ${previous_version} and earlier +- Stable branch: Continue using 'stable' for latest tested code + +Questions? Contact the CI Framework team. +EOF +} + +main() { + log "CI Framework Release Tagging Process Starting" + + # Ensure we're in the right directory + cd "$REPO_PATH" + + # Check if we're in a git repository + if ! git rev-parse --git-dir > /dev/null 2>&1; then + error "Not in a git repository" + exit 1 + fi + + # Fetch latest changes + log "Fetching latest changes..." + git fetch origin + + # Check out stable branch + log "Switching to ${STABLE_BRANCH} branch..." + git checkout "$STABLE_BRANCH" + git pull origin "$STABLE_BRANCH" + + # Calculate version + VERSION=$(get_current_version) + log "Target version: ${VERSION}" + + # Check if tag already exists + if tag_exists "$VERSION"; then + # Try patch version + PATCH_VERSION="${VERSION%.*}.1" + if tag_exists "$PATCH_VERSION"; then + error "Both ${VERSION} and ${PATCH_VERSION} already exist" + exit 1 + fi + VERSION="$PATCH_VERSION" + log "Base version exists, using patch version: ${VERSION}" + fi + + # Check stable branch activity + if ! check_stable_activity; then + if [ "$FORCE" != "true" ]; then + error "Stable branch has not been recently updated. Use FORCE=true to override." + exit 1 + fi + warn "Proceeding with stale stable branch due to FORCE=true" + fi + + # Get commit hash + COMMIT_HASH=$(get_stable_commit) + log "Stable branch at commit: ${COMMIT_HASH}" + + # Create the tag + if create_tag "$VERSION" "$COMMIT_HASH"; then + success "Release ${VERSION} created successfully!" + + # Generate announcement + log "Generating release announcement..." + ANNOUNCEMENT_FILE="/tmp/cifmw-release-${VERSION}.txt" + generate_announcement "$VERSION" > "$ANNOUNCEMENT_FILE" + log "Release announcement saved to: ${ANNOUNCEMENT_FILE}" + + log "Next steps:" + log "1. Review the release announcement" + log "2. Send announcement to stakeholders" + log "3. Update documentation if needed" + + else + error "Failed to create release ${VERSION}" + exit 1 + fi +} + +# Show usage +show_usage() { + cat << EOF +Usage: $0 [OPTIONS] + +Create a bi-weekly release tag from the stable branch. + +Options: + -h, --help Show this help message + -n, --dry-run Show what would be done without making changes + -f, --force Force tagging even if stable branch is stale + +Environment Variables: + REPO_PATH Path to repository (default: current directory) + STABLE_BRANCH Name of stable branch (default: stable) + DRY_RUN Set to 'true' for dry run mode + FORCE Set to 'true' to force tagging + +Examples: + $0 # Create release tag + $0 --dry-run # Show what would be done + FORCE=true $0 # Force tag creation even if stable is stale +EOF +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -n|--dry-run) + DRY_RUN="true" + shift + ;; + -f|--force) + FORCE="true" + shift + ;; + *) + error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Run main function +main + diff --git a/scripts/deprecation-status.py b/scripts/deprecation-status.py new file mode 100755 index 0000000000..119049e84f --- /dev/null +++ b/scripts/deprecation-status.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 +""" +Deprecation Status Dashboard +Tracks and reports on deprecation compliance across the CI Framework +""" + +import os +import re +import json +import yaml +from pathlib import Path +from datetime import datetime +from collections import defaultdict +from dataclasses import dataclass, asdict +from typing import List, Dict, Optional + + +@dataclass +class DeprecatedComponent: + name: str + type: str # playbook, role, task, etc. + file_path: str + deprecated_in: str + removal_target: str + alternative: Optional[str] + has_migration_doc: bool + has_runtime_warning: bool + weeks_until_removal: int + status: str # active, deprecated, overdue, removed + + +class DeprecationTracker: + def __init__(self, repo_path="."): + self.repo_path = Path(repo_path) + self.current_release = self._get_current_release() + self.deprecated_components: List[DeprecatedComponent] = [] + + def _get_current_release(self): + """Get current release version""" + try: + import subprocess + result = subprocess.run( + ['git', 'describe', '--tags', '--abbrev=0'], + capture_output=True, text=True, check=True, + cwd=self.repo_path + ) + return result.stdout.strip() + except: + # Fallback + now = datetime.now() + week = now.isocalendar()[1] + return f"{now.year}.{week:02d}.0" + + def _calculate_weeks_until_removal(self, removal_release): + """Calculate weeks until component removal""" + try: + # Parse releases (YYYY.WW.PATCH) + current_match = re.match(r'(\d{4})\.(\d+)\.(\d+)', self.current_release) + removal_match = re.match(r'(\d{4})\.(\d+)\.(\d+)', removal_release) + + if not current_match or not removal_match: + return -1 + + current_year = int(current_match.group(1)) + current_week = int(current_match.group(2)) + removal_year = int(removal_match.group(1)) + removal_week = int(removal_match.group(2)) + + # Calculate weeks difference accounting for year rollover + current_total_weeks = current_year * 52 + current_week + removal_total_weeks = removal_year * 52 + removal_week + + return removal_total_weeks - current_total_weeks + except: + return -1 + + def _check_migration_doc_exists(self, component_name): + """Check if migration documentation exists""" + doc_name = component_name.replace('/', '_') + migration_paths = [ + self.repo_path / f"docs/migration/{doc_name}.md", + self.repo_path / f"docs/deprecation/{doc_name}.md" + ] + return any(path.exists() for path in migration_paths) + + def _check_runtime_warning(self, file_path): + """Check if file contains runtime deprecation warnings""" + try: + with open(file_path, 'r') as f: + content = f.read() + + warning_patterns = [ + r'deprecat.*warning', + r'DEPRECATED', + r'will be removed', + r'migration.*required' + ] + + return any(re.search(pattern, content, re.IGNORECASE) for pattern in warning_patterns) + except: + return False + + def scan_deprecated_components(self): + """Scan repository for deprecated components""" + file_patterns = ['**/*.yml', '**/*.yaml', '**/*.py'] + + for pattern in file_patterns: + for file_path in self.repo_path.glob(pattern): + if any(skip in str(file_path) for skip in ['.git', '__pycache__', 'test']): + continue + + self._scan_file_for_deprecations(file_path) + + def _scan_file_for_deprecations(self, file_path): + """Scan individual file for deprecation notices""" + try: + with open(file_path, 'r') as f: + content = f.read() + + # Look for deprecation markers + deprecation_pattern = r'#\s*DEPRECATED:?\s*(.+?)(?:\n|$)' + removal_pattern = r'#\s*[Ww]ill be removed.*?(\d{4}\.S\d+\.\d+)' + alternative_pattern = r'#\s*(?:Migration|Use):?\s*(.+?)(?:\n|$)' + + deprecation_matches = re.findall(deprecation_pattern, content, re.MULTILINE) + removal_matches = re.findall(removal_pattern, content) + alternative_matches = re.findall(alternative_pattern, content) + + if deprecation_matches: + component_name = self._extract_component_name(file_path) + component_type = self._determine_component_type(file_path) + + removal_target = removal_matches[0] if removal_matches else "TBD" + alternative = alternative_matches[0].strip() if alternative_matches else None + + weeks_until = self._calculate_weeks_until_removal(removal_target) + + # Determine status + if weeks_until < 0: + status = "overdue" + elif weeks_until == 0: + status = "removal-due" + elif weeks_until <= 4: # 2 sprints + status = "removal-soon" + else: + status = "deprecated" + + component = DeprecatedComponent( + name=component_name, + type=component_type, + file_path=str(file_path), + deprecated_in=self.current_release, + removal_target=removal_target, + alternative=alternative, + has_migration_doc=self._check_migration_doc_exists(component_name), + has_runtime_warning=self._check_runtime_warning(file_path), + weeks_until_removal=weeks_until, + status=status + ) + + self.deprecated_components.append(component) + + except Exception as e: + print(f"Error scanning {file_path}: {e}") + + def _extract_component_name(self, file_path): + """Extract component name from file path""" + # Remove repo path and file extension + relative_path = file_path.relative_to(self.repo_path) + return str(relative_path).replace('.yml', '').replace('.yaml', '').replace('.py', '') + + def _determine_component_type(self, file_path): + """Determine component type from file path""" + path_str = str(file_path) + if '/playbooks/' in path_str: + return 'playbook' + elif '/roles/' in path_str: + return 'role' + elif '/tasks/' in path_str: + return 'task' + elif path_str.endswith('.py'): + return 'module' + else: + return 'unknown' + + def generate_compliance_report(self): + """Generate deprecation compliance report""" + if not self.deprecated_components: + return { + "status": "✅ No deprecated components found", + "summary": {"total": 0, "compliant": 0, "non_compliant": 0}, + "components": [] + } + + # Categorize components + compliant = [] + non_compliant = [] + + for component in self.deprecated_components: + issues = [] + + if not component.has_migration_doc: + issues.append("Missing migration documentation") + + if not component.has_runtime_warning: + issues.append("Missing runtime warnings") + + if component.removal_target == "TBD": + issues.append("No removal target specified") + + if component.weeks_until_removal < 0: + issues.append("Removal overdue") + + if issues: + non_compliant.append((component, issues)) + else: + compliant.append(component) + + # Generate report + report = { + "scan_date": datetime.now().isoformat(), + "current_release": self.current_release, + "summary": { + "total": len(self.deprecated_components), + "compliant": len(compliant), + "non_compliant": len(non_compliant), + "compliance_rate": f"{(len(compliant) / len(self.deprecated_components) * 100):.1f}%" + }, + "status_breakdown": self._get_status_breakdown(), + "compliant_components": [asdict(c) for c in compliant], + "non_compliant_components": [ + {"component": asdict(comp), "issues": issues} + for comp, issues in non_compliant + ] + } + + return report + + def _get_status_breakdown(self): + """Get breakdown by status""" + breakdown = defaultdict(int) + for component in self.deprecated_components: + breakdown[component.status] += 1 + return dict(breakdown) + + def print_dashboard(self): + """Print human-readable dashboard""" + report = self.generate_compliance_report() + + print("🗂️ CI Framework Deprecation Dashboard") + print("=" * 50) + print(f"📅 Scan Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"🏷️ Current Release: {self.current_release}") + print() + + summary = report["summary"] + print("📊 Summary:") + print(f" Total deprecated components: {summary['total']}") + print(f" Compliant: {summary['compliant']} ✅") + print(f" Non-compliant: {summary['non_compliant']} ❌") + print(f" Compliance rate: {summary['compliance_rate']}") + print() + + if report["status_breakdown"]: + print("📈 Status Breakdown:") + for status, count in report["status_breakdown"].items(): + emoji = {"deprecated": "⚠️", "removal-soon": "🚨", "overdue": "💥", "removal-due": "⏰"}.get(status, "❓") + print(f" {emoji} {status}: {count}") + print() + + # Show non-compliant components + if report["non_compliant_components"]: + print("❌ Non-Compliant Components:") + for item in report["non_compliant_components"]: + comp = item["component"] + issues = item["issues"] + print(f" 📁 {comp['name']} ({comp['type']})") + print(f" 📅 Removal: {comp['removal_target']} ({comp['weeks_until_removal']} weeks)") + print(f" 🚨 Issues:") + for issue in issues: + print(f" - {issue}") + print() + + def export_json_report(self, output_file): + """Export report as JSON""" + report = self.generate_compliance_report() + with open(output_file, 'w') as f: + json.dump(report, f, indent=2) + print(f"📄 Report exported to: {output_file}") + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='CI Framework Deprecation Status Dashboard') + parser.add_argument('--repo-path', default='.', help='Repository path to scan') + parser.add_argument('--export', help='Export JSON report to file') + parser.add_argument('--format', choices=['dashboard', 'json'], default='dashboard', + help='Output format') + + args = parser.parse_args() + + tracker = DeprecationTracker(args.repo_path) + tracker.scan_deprecated_components() + + if args.format == 'json' or args.export: + report = tracker.generate_compliance_report() + if args.export: + tracker.export_json_report(args.export) + else: + print(json.dumps(report, indent=2)) + else: + tracker.print_dashboard() + + +if __name__ == '__main__': + main() diff --git a/test-requirements.txt b/test-requirements.txt index f69ccac045..4eaf2240c2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,5 +24,9 @@ yamllint==1.35.1 pyspelling==2.10 mkdocs-pymdownx-material-extras==2.6 +# Conventional Commits Tools +gitlint==0.19.1 +commitizen==3.29.0 + # Common requirements ansi2txt==0.2.0