Skip to content

Release Automation Implementation Plan #332

@joshrotenberg

Description

@joshrotenberg

Release Automation Plan for redisctl

Executive Summary

Based on the issues encountered during the v0.5.1 release and requirements from issue #284, this plan outlines a comprehensive release automation strategy to reduce release time from ~30 minutes to ~5 minutes with minimal human intervention.

Problems Identified from v0.5.1 Release

  1. Version Synchronization Failure

  2. Manual Steps Required

    • Create release PR manually
    • Update changelog manually
    • Update version numbers in 4 places manually
    • Push tags manually after PR merge
  3. Protected Branch Restrictions

    • Cannot push directly to main
    • All changes require PR with CI passes
    • Complicates automated version bumping

Proposed Solution: release-plz

After evaluating options, release-plz is the recommended tool because:

  • Designed specifically for Rust workspace projects
  • Handles version bumping across all crates automatically
  • Generates changelogs from conventional commits
  • Creates release PRs automatically
  • Integrates well with GitHub Actions
  • Supports our protected branch workflow

Implementation Plan

Phase 1: Setup release-plz (Week 1)

1.1 Configuration File

Create release-plz.toml:

[workspace]
# Update all crates together
allow_dirty = false
consolidate_commits = true
git_release_enable = true
git_tag_enable = true
publish_allow_dirty = false

# Changelog generation
changelog_update = true
changelog_include = ["feat", "fix", "perf", "refactor", "docs", "test", "chore"]

# Version bumping rules
version_scheme = "semver"
dependencies_update = true

[[package]]
name = "redisctl"
# Main CLI gets its own tag format
tag_name = "v{{ version }}"

[[package]]
name = "redis-cloud"
# Library crates get prefixed tags
tag_name = "redis-cloud-v{{ version }}"

[[package]]
name = "redis-enterprise"
tag_name = "redis-enterprise-v{{ version }}"

1.2 GitHub Action Workflow

Create .github/workflows/release-plz.yml:

name: Release-plz

permissions:
  contents: write
  pull-requests: write

on:
  push:
    branches: [ main ]
  schedule:
    # Run weekly to check for updates
    - cron: '0 9 * * 1'
  workflow_dispatch:

jobs:
  release-plz:
    name: Release-plz
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      
      - name: Run release-plz
        uses: MarcoIeni/release-plz-action@v0.5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

Phase 2: Integration with cargo-dist (Week 1)

2.1 Update Release Workflow

Modify .github/workflows/release.yml to trigger on release-plz tags:

on:
  push:
    tags:
      - 'v[0-9]+.*'           # Main CLI releases
      - '*-v[0-9]+.*'         # Library releases
  workflow_dispatch:

2.2 Version Validation

Add pre-release checks:

- name: Validate versions
  run: |
    # Ensure all versions match the tag
    TAG_VERSION="${GITHUB_REF#refs/tags/v}"
    CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "redisctl") | .version')
    if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
      echo "Version mismatch: tag=$TAG_VERSION cargo=$CARGO_VERSION"
      exit 1
    fi

Phase 3: Automated Testing & Validation (Week 2)

3.1 Pre-release Checklist Script

Create scripts/pre-release-check.sh:

#!/bin/bash
set -e

echo "🔍 Running pre-release checks..."

# 1. Check all versions are synchronized
echo "Checking version synchronization..."
WORKSPACE_VERSION=$(grep "^version" Cargo.toml | head -1 | cut -d'"' -f2)
for crate in redisctl redis-cloud redis-enterprise; do
  CRATE_VERSION=$(grep "^version" crates/$crate/Cargo.toml | cut -d'"' -f2)
  if [ "$WORKSPACE_VERSION" != "$CRATE_VERSION" ]; then
    echo "❌ Version mismatch: workspace=$WORKSPACE_VERSION $crate=$CRATE_VERSION"
    exit 1
  fi
done
echo "✅ All versions synchronized: $WORKSPACE_VERSION"

# 2. Run full test suite
echo "Running tests..."
cargo test --workspace --all-features

# 3. Check for uncommitted changes
if ! git diff --quiet; then
  echo "❌ Uncommitted changes detected"
  exit 1
fi

echo "✅ All pre-release checks passed!"

3.2 Add to CI Pipeline

- name: Pre-release validation
  run: ./scripts/pre-release-check.sh

Phase 4: Rollback & Recovery (Week 2)

4.1 State Tracking

Create .release-state.json to track release progress:

{
  "version": "0.5.1",
  "stage": "preparation",
  "started": "2024-01-15T10:00:00Z",
  "pr_number": 330,
  "tag_pushed": false,
  "crates_published": []
}

4.2 Rollback Script

Create scripts/rollback-release.sh:

#!/bin/bash
# Rollback a failed release based on current state

STATE_FILE=".release-state.json"
if [ ! -f "$STATE_FILE" ]; then
  echo "No release in progress"
  exit 0
fi

STAGE=$(jq -r '.stage' $STATE_FILE)
case $STAGE in
  "preparation")
    echo "Rolling back version changes..."
    git reset --hard HEAD~1
    ;;
  "staging")
    echo "Closing PR and deleting branch..."
    PR_NUMBER=$(jq -r '.pr_number' $STATE_FILE)
    gh pr close $PR_NUMBER
    git push origin --delete release/v$(jq -r '.version' $STATE_FILE)
    ;;
  *)
    echo "Cannot rollback from stage: $STAGE"
    exit 1
    ;;
esac

rm $STATE_FILE
echo "✅ Rollback complete"

Phase 5: Manual Override & Dry Run (Week 3)

5.1 Manual Release Script

For emergency releases or special cases:

# scripts/manual-release.sh
#!/bin/bash
VERSION=${1:-patch}
DRY_RUN=${2:-false}

if [ "$DRY_RUN" = "true" ]; then
  echo "🔍 DRY RUN MODE"
  release-plz release --dry-run
else
  release-plz release
fi

5.2 Interactive Release CLI

Create a Rust binary for guided releases:

// crates/release-tool/src/main.rs
use clap::Parser;
use dialoguer::{Select, Confirm};

#[derive(Parser)]
struct Args {
    #[arg(long)]
    dry_run: bool,
}

fn main() -> Result<()> {
    let args = Args::parse();
    
    let version_type = Select::new()
        .with_prompt("Select version bump type")
        .items(&["patch", "minor", "major", "custom"])
        .default(0)
        .interact()?;
    
    if !args.dry_run {
        let confirm = Confirm::new()
            .with_prompt("Ready to create release?")
            .interact()?;
        
        if !confirm {
            return Ok(());
        }
    }
    
    // Execute release-plz
    Ok(())
}

Migration Strategy

Week 1: Setup & Testing

  1. Install release-plz locally
  2. Test with dry-run on feature branch
  3. Configure GitHub Actions
  4. Document process in RELEASING.md

Week 2: Pilot Release

  1. Create v0.5.2 as pilot release
  2. Monitor automated PR creation
  3. Verify changelog generation
  4. Test rollback procedures

Week 3: Full Automation

  1. Remove manual release documentation
  2. Archive old release scripts
  3. Train team on new process
  4. Create runbook for edge cases

Success Metrics

Metric Current Target Measurement
Release Time ~30 min <5 min GitHub Action runtime
Manual Steps 8+ 1 (merge PR) Count of human actions
Version Sync Errors 1 in v0.5.1 0 Release failures due to versions
Rollback Time N/A <2 min Time to revert failed release

Risk Mitigation

Risk Probability Impact Mitigation
release-plz bugs Low High Maintain manual fallback
CI token permissions Medium Medium Use fine-grained PATs
Partial publish failure Low High State tracking & rollback
Wrong version bump Medium Low PR review before merge

Decision Points

  1. Q: Should we version all crates together or independently?

    • A: Together initially, can split later if needed
  2. Q: How to handle hotfixes?

    • A: Manual override with release-plz release --version X.Y.Z
  3. Q: What about pre-releases (RC, beta)?

    • A: Use --pre-release flag and manual workflow

Implementation Checklist

  • Create release-plz.toml configuration
  • Set up GitHub Action for release-plz
  • Add CARGO_REGISTRY_TOKEN secret
  • Update release.yml for new tag format
  • Create pre-release validation script
  • Implement state tracking
  • Write rollback procedures
  • Test dry-run on feature branch
  • Document in RELEASING.md
  • Archive manual release docs
  • Pilot release v0.5.2
  • Team training session
  • Create emergency runbook

Next Steps

  1. Immediate: Get v0.5.1 released with current manual process
  2. This Week: Create feature branch with release-plz setup
  3. Next Week: Test automation with v0.5.2 pilot
  4. Two Weeks: Full automation live

References

References

This plan synthesizes the requirements from #284 with real-world experience from our v0.5.1 release problems.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions