This commit is contained in:
Toby
2026-01-27 17:35:45 -05:00
commit 2639af6531
176 changed files with 27104 additions and 0 deletions
+501
View File
@@ -0,0 +1,501 @@
---
name: blue-green-deploy
description: Configure zero-downtime deployment strategies including blue-green, canary, and rolling deployments. Implement traffic shifting, health checks, and rollback procedures. Use when implementing production deployment strategies or zero-downtime releases.
license: MIT
metadata:
author: devops-skills
version: "1.0"
---
# Blue-Green & Deployment Strategies
Implement zero-downtime deployment patterns for production systems.
## When to Use This Skill
Use this skill when:
- Implementing zero-downtime deployments
- Reducing deployment risk
- Enabling instant rollbacks
- Running canary releases
- Performing A/B testing in production
## Prerequisites
- Load balancer or ingress controller
- Container orchestration (K8s) or cloud platform
- CI/CD pipeline
- Health check endpoints
## Deployment Strategy Overview
```
┌─────────────────────────────────────────────────────────────┐
│ DEPLOYMENT STRATEGIES │
├─────────────┬─────────────┬─────────────┬──────────────────┤
│ Blue-Green │ Canary │ Rolling │ Recreate │
├─────────────┼─────────────┼─────────────┼──────────────────┤
│ Full env │ Gradual % │ Pod by pod │ All at once │
│ swap │ rollout │ replacement │ │
├─────────────┼─────────────┼─────────────┼──────────────────┤
│ Instant │ Slow, safe │ Moderate │ Fast, risky │
│ rollback │ rollback │ rollback │ │
├─────────────┼─────────────┼─────────────┼──────────────────┤
│ 2x resources│ +10-25% │ Same │ Same │
│ needed │ resources │ resources │ │
└─────────────┴─────────────┴─────────────┴──────────────────┘
```
## Blue-Green Deployment
### Concept
```
Before:
┌─────────┐ ┌───────────────┐
│ Users │────▶│ Blue (v1) │ ◀── Active
└─────────┘ └───────────────┘
┌───────────────┐
│ Green (v2) │ ◀── Staging
└───────────────┘
After Switch:
┌─────────┐ ┌───────────────┐
│ Users │ │ Blue (v1) │ ◀── Standby
└─────────┘ └───────────────┘
│ ┌───────────────┐
└────────▶│ Green (v2) │ ◀── Active
└───────────────┘
```
### Kubernetes Implementation
```yaml
# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-blue
labels:
app: myapp
version: blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: myapp
image: myapp:v1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
labels:
app: myapp
version: green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: myapp
image: myapp:v2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
# service.yaml - Switch by changing selector
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
version: blue # Change to 'green' to switch
ports:
- port: 80
targetPort: 8080
```
### Switch Script
```bash
#!/bin/bash
# blue-green-switch.sh
CURRENT=$(kubectl get svc myapp -o jsonpath='{.spec.selector.version}')
NEW_VERSION=$1
echo "Current version: $CURRENT"
echo "Switching to: $NEW_VERSION"
# Verify new deployment is ready
kubectl rollout status deployment/myapp-$NEW_VERSION
# Check health
HEALTH=$(kubectl exec -it deployment/myapp-$NEW_VERSION -- curl -s localhost:8080/health)
if [ "$HEALTH" != "ok" ]; then
echo "Health check failed"
exit 1
fi
# Switch traffic
kubectl patch svc myapp -p "{\"spec\":{\"selector\":{\"version\":\"$NEW_VERSION\"}}}"
echo "Switched to $NEW_VERSION"
```
### AWS ECS Blue-Green
```yaml
# AWS CodeDeploy appspec.yml
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "arn:aws:ecs:region:account:task-definition/myapp:2"
LoadBalancerInfo:
ContainerName: "myapp"
ContainerPort: 8080
Hooks:
- BeforeInstall: "LambdaFunctionToValidateBeforeTrafficShift"
- AfterInstall: "LambdaFunctionToValidateAfterTrafficShift"
- AfterAllowTestTraffic: "LambdaFunctionToValidateTestTraffic"
- BeforeAllowTraffic: "LambdaFunctionToValidateBeforeAllowTraffic"
- AfterAllowTraffic: "LambdaFunctionToValidateAfterAllowTraffic"
```
## Canary Deployment
### Kubernetes with Istio
```yaml
# VirtualService for traffic splitting
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: myapp
subset: canary
- route:
- destination:
host: myapp
subset: stable
weight: 90
- destination:
host: myapp
subset: canary
weight: 10
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: myapp
spec:
host: myapp
subsets:
- name: stable
labels:
version: stable
- name: canary
labels:
version: canary
```
### Argo Rollouts
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 25
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 75
- pause: {duration: 5m}
analysis:
templates:
- templateName: success-rate
startingStep: 2
args:
- name: service-name
value: myapp
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v2.0.0
ports:
- containerPort: 8080
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{service="{{args.service-name}}",status=~"2.*"}[5m]))
/
sum(rate(http_requests_total{service="{{args.service-name}}"}[5m]))
```
## Rolling Deployment
### Kubernetes Default
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max pods above desired
maxUnavailable: 0 # Max pods unavailable
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
```
### Rolling Update Commands
```bash
# Update image
kubectl set image deployment/myapp myapp=myapp:v2.0.0
# Watch rollout
kubectl rollout status deployment/myapp
# Pause rollout
kubectl rollout pause deployment/myapp
# Resume rollout
kubectl rollout resume deployment/myapp
# Rollback
kubectl rollout undo deployment/myapp
# Rollback to specific revision
kubectl rollout undo deployment/myapp --to-revision=2
# View history
kubectl rollout history deployment/myapp
```
## Health Checks
### Comprehensive Health Endpoint
```python
# Flask health endpoint
from flask import Flask, jsonify
import psycopg2
import redis
app = Flask(__name__)
@app.route('/health')
def health():
"""Liveness probe - is the app running?"""
return jsonify({'status': 'healthy'}), 200
@app.route('/ready')
def ready():
"""Readiness probe - can the app serve traffic?"""
checks = {}
# Database check
try:
conn = psycopg2.connect(DATABASE_URL)
conn.close()
checks['database'] = 'ok'
except Exception as e:
checks['database'] = str(e)
return jsonify({'status': 'unhealthy', 'checks': checks}), 503
# Redis check
try:
r = redis.from_url(REDIS_URL)
r.ping()
checks['redis'] = 'ok'
except Exception as e:
checks['redis'] = str(e)
return jsonify({'status': 'unhealthy', 'checks': checks}), 503
return jsonify({'status': 'healthy', 'checks': checks}), 200
```
## Rollback Procedures
### Automated Rollback
```bash
#!/bin/bash
# auto-rollback.sh
DEPLOYMENT=$1
THRESHOLD=0.95
INTERVAL=60
echo "Monitoring deployment $DEPLOYMENT"
while true; do
# Get success rate from Prometheus
SUCCESS_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=sum(rate(http_requests_total{status=~\"2.*\"}[5m]))/sum(rate(http_requests_total[5m]))" | jq -r '.data.result[0].value[1]')
echo "Current success rate: $SUCCESS_RATE"
if (( $(echo "$SUCCESS_RATE < $THRESHOLD" | bc -l) )); then
echo "Success rate below threshold! Rolling back..."
kubectl rollout undo deployment/$DEPLOYMENT
exit 1
fi
sleep $INTERVAL
done
```
### Manual Rollback Checklist
```markdown
## Rollback Checklist
### Before Rollback
- [ ] Confirm issue is deployment-related
- [ ] Document current error rates
- [ ] Notify team in #deployments channel
### During Rollback
- [ ] Execute rollback command
- [ ] Monitor rollback progress
- [ ] Verify old version is serving traffic
### After Rollback
- [ ] Confirm error rates normalized
- [ ] Update incident ticket
- [ ] Schedule post-mortem
```
## Common Issues
### Issue: Slow Deployments
**Problem**: Rollout takes too long
**Solution**: Increase maxSurge, decrease minReadySeconds
### Issue: Failed Health Checks
**Problem**: Pods not becoming ready
**Solution**: Check probe endpoints, increase timeouts
### Issue: Traffic During Rollback
**Problem**: Errors during switch
**Solution**: Use connection draining, implement graceful shutdown
## Best Practices
- Always implement health checks
- Use connection draining
- Test rollback procedures regularly
- Monitor key metrics during deployment
- Implement circuit breakers
- Use deployment slots/environments
- Automate deployment verification
- Document rollback procedures
## Related Skills
- [kubernetes-ops](../../orchestration/kubernetes-ops/) - K8s deployment basics
- [argocd-gitops](../../orchestration/argocd-gitops/) - GitOps deployments
- [feature-flags](../feature-flags/) - Progressive rollout
+472
View File
@@ -0,0 +1,472 @@
---
name: feature-flags
description: Implement feature flags for progressive feature rollout using LaunchDarkly, Unleash, or custom solutions. Control feature visibility, perform A/B testing, and enable trunk-based development. Use when implementing gradual rollouts, feature toggles, or experimentation platforms.
license: MIT
metadata:
author: devops-skills
version: "1.0"
---
# Feature Flags
Control feature releases and enable progressive rollout with feature flag systems.
## When to Use This Skill
Use this skill when:
- Implementing gradual feature rollouts
- Enabling trunk-based development
- Running A/B tests and experiments
- Managing feature lifecycles
- Implementing kill switches for production
## Prerequisites
- Application code access
- Feature flag service or self-hosted solution
- Basic understanding of deployment patterns
## Feature Flag Types
| Type | Purpose | Example |
|------|---------|---------|
| Release | Control feature visibility | New checkout flow |
| Experiment | A/B testing | Button color test |
| Ops | Runtime configuration | Rate limiting |
| Permission | User access control | Premium features |
| Kill Switch | Emergency disable | Third-party integration |
## LaunchDarkly
### SDK Setup (Node.js)
```javascript
const LaunchDarkly = require('launchdarkly-node-server-sdk');
const client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY);
await client.waitForInitialization();
// Evaluate flag
const user = {
key: 'user-123',
email: 'user@example.com',
custom: {
plan: 'premium',
company: 'acme'
}
};
const showNewFeature = await client.variation('new-checkout', user, false);
if (showNewFeature) {
// New feature code
} else {
// Existing code
}
```
### React SDK
```javascript
import { withLDProvider, useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
// Provider setup
export default withLDProvider({
clientSideID: 'your-client-side-id',
user: {
key: 'user-123',
email: 'user@example.com'
}
})(App);
// Using flags in component
function FeatureComponent() {
const { newCheckout, experimentVariant } = useFlags();
const ldClient = useLDClient();
// Track events
const handleClick = () => {
ldClient.track('checkout-started');
};
if (newCheckout) {
return <NewCheckout onClick={handleClick} />;
}
return <OldCheckout onClick={handleClick} />;
}
```
### Targeting Rules
```yaml
# LaunchDarkly targeting configuration
flag: new-checkout
targeting:
# Individual users
targets:
- variation: true
values: ['user-123', 'user-456']
# Rules
rules:
# Beta users
- variation: true
clauses:
- attribute: email
op: endsWith
values: ['@company.com']
# Premium plan
- variation: true
clauses:
- attribute: plan
op: in
values: ['premium', 'enterprise']
# Percentage rollout
- variation: true
rollout:
variations:
- variation: true
weight: 20000 # 20%
- variation: false
weight: 80000 # 80%
# Default
fallthrough:
variation: false
```
## Unleash
### Server Setup
```yaml
# docker-compose.yml
version: '3.8'
services:
unleash:
image: unleashorg/unleash-server:latest
ports:
- "4242:4242"
environment:
- DATABASE_URL=postgres://postgres:password@db/unleash
- DATABASE_SSL=false
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=unleash
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:
```
### SDK Setup (Node.js)
```javascript
const { initialize } = require('unleash-client');
const unleash = initialize({
url: 'http://localhost:4242/api',
appName: 'my-app',
customHeaders: {
Authorization: 'your-api-token'
}
});
unleash.on('ready', () => {
// Check feature
const isEnabled = unleash.isEnabled('new-checkout');
// With context
const context = {
userId: 'user-123',
properties: {
plan: 'premium'
}
};
const isEnabledForUser = unleash.isEnabled('new-checkout', context);
// Get variant
const variant = unleash.getVariant('experiment-flag', context);
console.log(variant.name); // 'control' or 'treatment'
});
```
### Activation Strategies
```yaml
# Standard strategies
strategies:
- name: default
# On/off for everyone
- name: userWithId
parameters:
userIds: 'user-1,user-2,user-3'
- name: gradualRolloutUserId
parameters:
percentage: 25
groupId: 'new-feature'
- name: gradualRolloutRandom
parameters:
percentage: 50
- name: flexibleRollout
parameters:
rollout: 30
stickiness: userId
groupId: 'checkout-exp'
```
## Custom Implementation
### Database-Backed Flags
```python
# models.py
from django.db import models
class FeatureFlag(models.Model):
name = models.CharField(max_length=100, unique=True)
enabled = models.BooleanField(default=False)
rollout_percentage = models.IntegerField(default=0)
allowed_users = models.JSONField(default=list)
rules = models.JSONField(default=dict)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# service.py
import hashlib
class FeatureFlagService:
def __init__(self):
self._cache = {}
def is_enabled(self, flag_name, user_id=None, context=None):
flag = self._get_flag(flag_name)
if not flag or not flag.enabled:
return False
# Check user allowlist
if user_id and user_id in flag.allowed_users:
return True
# Check rules
if context and self._evaluate_rules(flag.rules, context):
return True
# Check percentage rollout
if flag.rollout_percentage > 0 and user_id:
return self._is_in_rollout(flag_name, user_id, flag.rollout_percentage)
return flag.rollout_percentage == 100
def _is_in_rollout(self, flag_name, user_id, percentage):
hash_input = f"{flag_name}:{user_id}"
hash_value = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)
return (hash_value % 100) < percentage
def _evaluate_rules(self, rules, context):
for rule in rules.get('rules', []):
if self._evaluate_rule(rule, context):
return True
return False
```
### Redis-Backed Flags
```python
import redis
import json
class RedisFeatureFlags:
def __init__(self, redis_url):
self.redis = redis.from_url(redis_url)
self.prefix = 'feature_flag:'
def set_flag(self, name, config):
key = f"{self.prefix}{name}"
self.redis.set(key, json.dumps(config))
def is_enabled(self, name, user_id=None):
key = f"{self.prefix}{name}"
data = self.redis.get(key)
if not data:
return False
config = json.loads(data)
if not config.get('enabled', False):
return False
# User allowlist
if user_id in config.get('users', []):
return True
# Percentage rollout
percentage = config.get('percentage', 0)
if percentage == 100:
return True
if percentage > 0 and user_id:
return self._hash_user(name, user_id) < percentage
return False
def _hash_user(self, flag, user_id):
import hashlib
hash_input = f"{flag}:{user_id}"
return int(hashlib.sha256(hash_input.encode()).hexdigest(), 16) % 100
```
## Testing with Feature Flags
### Unit Testing
```javascript
// Jest mocking
jest.mock('launchdarkly-node-server-sdk', () => ({
init: jest.fn(() => ({
waitForInitialization: jest.fn().mockResolvedValue(undefined),
variation: jest.fn()
}))
}));
describe('Checkout', () => {
it('shows new checkout when flag enabled', async () => {
const ldClient = require('launchdarkly-node-server-sdk').init();
ldClient.variation.mockResolvedValue(true);
const result = await renderCheckout(user);
expect(result).toContain('NewCheckout');
});
it('shows old checkout when flag disabled', async () => {
const ldClient = require('launchdarkly-node-server-sdk').init();
ldClient.variation.mockResolvedValue(false);
const result = await renderCheckout(user);
expect(result).toContain('OldCheckout');
});
});
```
### Integration Testing
```python
# pytest fixtures
import pytest
@pytest.fixture
def feature_flags():
"""Provide controllable feature flags for testing."""
flags = {}
class TestFlags:
def set(self, name, value):
flags[name] = value
def is_enabled(self, name, **kwargs):
return flags.get(name, False)
return TestFlags()
def test_new_checkout(feature_flags):
feature_flags.set('new-checkout', True)
response = client.get('/checkout')
assert 'new-checkout-form' in response.content
```
## Monitoring and Analytics
### Flag Usage Tracking
```javascript
// Track flag evaluations
const flagMetrics = {
evaluations: new Map(),
track(flagName, variation, user) {
const key = `${flagName}:${variation}`;
const count = this.evaluations.get(key) || 0;
this.evaluations.set(key, count + 1);
// Send to analytics
analytics.track('feature_flag_evaluated', {
flag: flagName,
variation: variation,
userId: user.key
});
}
};
```
### Stale Flag Detection
```python
from datetime import datetime, timedelta
def detect_stale_flags():
"""Find flags that haven't been evaluated recently."""
stale_threshold = timedelta(days=30)
now = datetime.utcnow()
stale_flags = []
for flag in FeatureFlag.objects.all():
if flag.last_evaluated:
age = now - flag.last_evaluated
if age > stale_threshold:
stale_flags.append({
'name': flag.name,
'last_evaluated': flag.last_evaluated,
'age_days': age.days
})
return stale_flags
```
## Common Issues
### Issue: Inconsistent Flag Evaluation
**Problem**: Same user sees different variations
**Solution**: Use consistent hashing, check caching strategy
### Issue: Flag Debt Accumulation
**Problem**: Too many old flags in codebase
**Solution**: Implement flag lifecycle, regular cleanup sprints
### Issue: Performance Impact
**Problem**: Flag evaluation slowing requests
**Solution**: Use local caching, batch evaluations
## Best Practices
- Use consistent naming conventions
- Document flag purpose and owner
- Set expiration dates for temporary flags
- Implement flag lifecycle management
- Use gradual rollouts (not 0→100)
- Monitor flag evaluation metrics
- Clean up old flags regularly
- Test both variations in CI
## Related Skills
- [blue-green-deploy](../blue-green-deploy/) - Deployment strategies
- [git-workflow](../git-workflow/) - Trunk-based development
- [alerting-oncall](../../observability/alerting-oncall/) - Monitoring rollouts
+435
View File
@@ -0,0 +1,435 @@
---
name: git-workflow
description: Implement Git branching strategies, PR workflows, and release management patterns. Configure GitFlow, trunk-based development, or GitHub Flow for team collaboration. Use when establishing version control workflows or improving development team collaboration.
license: MIT
metadata:
author: devops-skills
version: "1.0"
---
# Git Workflow
Implement effective branching strategies and pull request workflows for team collaboration.
## When to Use This Skill
Use this skill when:
- Establishing team Git workflows
- Implementing branching strategies
- Configuring pull request processes
- Setting up release management
- Improving code review practices
## Prerequisites
- Git installed
- Repository hosting (GitHub, GitLab, Bitbucket)
- Basic Git knowledge
## Branching Strategies
### Trunk-Based Development
Best for: Continuous deployment, small teams, mature CI/CD
```
main ─────●─────●─────●─────●─────●─────●─────●
│ │ │ │ │ │
└─● └─● └─● └─● └─● └─●
feature branches (short-lived)
```
```bash
# Create short-lived feature branch
git checkout main
git pull origin main
git checkout -b feature/add-login
# Work and commit frequently
git add .
git commit -m "feat: add login form"
# Keep branch updated
git fetch origin
git rebase origin/main
# Merge quickly (same day ideally)
git checkout main
git pull origin main
git merge feature/add-login
git push origin main
git branch -d feature/add-login
```
### GitHub Flow
Best for: Continuous delivery, web applications
```
main ─────●─────●───────────●─────────────●─────●
│ ↑ ↑ ↑
└───●───●───┘ │ │
feature/login │ │
│ │
└───●───●───●───●───────┘ │
feature/dashboard │
└───●─────────────────────────┘
hotfix/security-patch
```
```bash
# Create feature branch from main
git checkout main
git pull origin main
git checkout -b feature/user-dashboard
# Push and create PR
git push -u origin feature/user-dashboard
# After review, merge via PR (squash recommended)
# Delete branch after merge
```
### GitFlow
Best for: Scheduled releases, versioned products
```
main ────────●────────────────●──────────────●
↑ ↑ ↑
release ────────┼────●───●──────┼──────────────┼
│ │ │ │ │
develop ───●────●────┼───●──●───●───●───●───●──┼
│ │ │ │ │ │
feature ───┴─────────┘ │ │ │ │
│ │ │ │
hotfix ────────────────────┴───────┼───┼──────┘
│ │
feature ────────────────────────────┴───┘
```
```bash
# Initialize GitFlow
git flow init
# Start feature
git flow feature start user-auth
# Finish feature (merges to develop)
git flow feature finish user-auth
# Start release
git flow release start 1.0.0
# Finish release (merges to main and develop)
git flow release finish 1.0.0
# Hotfix
git flow hotfix start security-fix
git flow hotfix finish security-fix
```
## Commit Conventions
### Conventional Commits
```
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
```
Types:
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation
- `style`: Formatting
- `refactor`: Code restructuring
- `test`: Adding tests
- `chore`: Maintenance
Examples:
```bash
git commit -m "feat(auth): add OAuth2 login support"
git commit -m "fix(api): handle null response from payment service"
git commit -m "docs: update API documentation for v2 endpoints"
git commit -m "refactor(db): optimize user query performance"
# Breaking change
git commit -m "feat(api)!: change response format for user endpoint
BREAKING CHANGE: The user endpoint now returns an object instead of array"
```
### Commit Message Template
```bash
# Create template file ~/.gitmessage
# Subject line (50 chars max)
# Body (72 chars per line max)
# - What changed
# - Why it changed
# - Any side effects
# Footer
# Fixes #123
# Co-authored-by: Name <email>
# Configure Git to use template
git config --global commit.template ~/.gitmessage
```
## Pull Request Workflow
### PR Template
```markdown
<!-- .github/pull_request_template.md -->
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing performed
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review performed
- [ ] Documentation updated
- [ ] No new warnings introduced
## Related Issues
Closes #
## Screenshots (if applicable)
```
### Branch Protection Rules
```yaml
# GitHub branch protection
branch_protection:
branch: main
required_pull_request_reviews:
required_approving_review_count: 1
dismiss_stale_reviews: true
require_code_owner_reviews: true
required_status_checks:
strict: true
contexts:
- "ci/tests"
- "ci/lint"
restrictions:
users: []
teams: ["maintainers"]
enforce_admins: true
required_linear_history: true
allow_force_pushes: false
allow_deletions: false
```
### Code Owners
```
# .github/CODEOWNERS
# Default owners
* @team-leads
# Frontend code
/src/frontend/ @frontend-team
*.tsx @frontend-team
*.css @frontend-team
# Backend code
/src/api/ @backend-team
/src/services/ @backend-team
# Infrastructure
/terraform/ @platform-team
/k8s/ @platform-team
Dockerfile @platform-team
# Documentation
/docs/ @tech-writers
*.md @tech-writers
```
## Git Hooks
### Pre-commit Hook
```bash
#!/bin/sh
# .git/hooks/pre-commit
# Run linting
npm run lint
if [ $? -ne 0 ]; then
echo "Linting failed. Fix errors before committing."
exit 1
fi
# Run tests
npm run test:unit
if [ $? -ne 0 ]; then
echo "Tests failed. Fix tests before committing."
exit 1
fi
# Check for debug statements
if grep -r "console.log\|debugger\|binding.pry" --include="*.js" --include="*.ts" --include="*.rb" src/; then
echo "Remove debug statements before committing."
exit 1
fi
```
### Using Husky
```json
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["prettier --write"]
}
}
```
### Commitlint Configuration
```javascript
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert']
],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100]
}
};
```
## Release Workflow
### Automated Release with Tags
```bash
# Create annotated tag
git tag -a v1.0.0 -m "Release version 1.0.0"
# Push tag
git push origin v1.0.0
# Create release from tag (GitHub CLI)
gh release create v1.0.0 \
--title "Release 1.0.0" \
--notes "Release notes here" \
--target main
```
### Changelog Generation
```bash
# Using conventional-changelog
npx conventional-changelog -p angular -i CHANGELOG.md -s
# Using git-cliff
git cliff -o CHANGELOG.md
```
## Common Git Operations
### Rebase vs Merge
```bash
# Rebase (clean history)
git checkout feature/my-feature
git rebase main
git push --force-with-lease
# Merge (preserve history)
git checkout main
git merge feature/my-feature
# Squash merge (single commit)
git merge --squash feature/my-feature
git commit -m "feat: add feature X"
```
### Cherry Pick
```bash
# Apply specific commit to current branch
git cherry-pick abc123
# Cherry pick range
git cherry-pick abc123..def456
# Cherry pick without committing
git cherry-pick -n abc123
```
### Interactive Rebase
```bash
# Clean up last 3 commits
git rebase -i HEAD~3
# In editor:
# pick abc123 First commit
# squash def456 Second commit
# reword ghi789 Third commit
```
## Common Issues
### Issue: Merge Conflicts
**Problem**: Conflicts when merging branches
**Solution**: Rebase frequently, communicate with team, use smaller PRs
### Issue: Diverged Branches
**Problem**: Local branch far behind remote
**Solution**: `git pull --rebase` or `git fetch && git rebase origin/main`
### Issue: Accidental Commit to Wrong Branch
**Problem**: Committed to main instead of feature
**Solution**: `git reset HEAD~1`, checkout correct branch, recommit
## Best Practices
- Keep branches short-lived (< 1 week)
- Write meaningful commit messages
- Use PR templates consistently
- Require code reviews
- Protect main/master branch
- Automate checks with CI
- Squash merge for clean history
- Delete branches after merge
## Related Skills
- [semantic-versioning](../semantic-versioning/) - Version management
- [github-actions](../../ci-cd/github-actions/) - CI/CD automation
- [feature-flags](../feature-flags/) - Feature management
+477
View File
@@ -0,0 +1,477 @@
---
name: semantic-versioning
description: Automate versioning and changelog generation using semantic versioning principles. Configure release automation, version bumping, and changelog tools. Use when implementing version management or automating release processes.
license: MIT
metadata:
author: devops-skills
version: "1.0"
---
# Semantic Versioning
Automate version management and changelog generation following SemVer principles.
## When to Use This Skill
Use this skill when:
- Implementing version numbering standards
- Automating release versioning
- Generating changelogs automatically
- Setting up release pipelines
- Managing package versions
## Prerequisites
- Git repository with commit history
- Node.js (for most tools)
- Conventional commits (recommended)
## Semantic Versioning Basics
### Version Format
```
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
Examples:
1.0.0
2.1.3
1.0.0-alpha.1
1.0.0-beta.2+build.123
```
### Version Components
| Component | When to Increment |
|-----------|-------------------|
| MAJOR | Breaking changes (incompatible API changes) |
| MINOR | New features (backward compatible) |
| PATCH | Bug fixes (backward compatible) |
| PRERELEASE | Pre-release versions (alpha, beta, rc) |
| BUILD | Build metadata (ignored in precedence) |
### Version Precedence
```
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta
< 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11
< 1.0.0-rc.1 < 1.0.0 < 2.0.0
```
## Conventional Commits to Version
```yaml
Commit Type → Version Bump:
feat: → MINOR
fix: → PATCH
docs: → PATCH (or no release)
style: → PATCH (or no release)
refactor: → PATCH
perf: → PATCH
test: → No release
chore: → No release
BREAKING CHANGE: → MAJOR
feat!: → MAJOR
fix!: → MAJOR
```
## semantic-release
### Installation
```bash
npm install --save-dev semantic-release \
@semantic-release/changelog \
@semantic-release/git
```
### Configuration
```json
// .releaserc.json
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/changelog", {
"changelogFile": "CHANGELOG.md"
}],
["@semantic-release/npm", {
"npmPublish": true
}],
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
}
```
### Advanced Configuration
```javascript
// release.config.js
module.exports = {
branches: [
'main',
{ name: 'beta', prerelease: true },
{ name: 'alpha', prerelease: true }
],
plugins: [
['@semantic-release/commit-analyzer', {
preset: 'angular',
releaseRules: [
{ type: 'docs', release: 'patch' },
{ type: 'refactor', release: 'patch' },
{ type: 'style', release: 'patch' },
{ type: 'perf', release: 'patch' },
{ breaking: true, release: 'major' }
]
}],
['@semantic-release/release-notes-generator', {
preset: 'angular',
writerOpts: {
commitsSort: ['subject', 'scope']
}
}],
'@semantic-release/changelog',
'@semantic-release/npm',
'@semantic-release/git',
'@semantic-release/github'
]
};
```
### GitHub Actions Integration
```yaml
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
```
## standard-version
### Installation
```bash
npm install --save-dev standard-version
```
### Configuration
```json
// .versionrc.json
{
"types": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "docs", "section": "Documentation" },
{ "type": "style", "section": "Styling" },
{ "type": "refactor", "section": "Code Refactoring" },
{ "type": "perf", "section": "Performance" },
{ "type": "test", "section": "Tests" },
{ "type": "chore", "section": "Maintenance" }
],
"skip": {
"bump": false,
"changelog": false,
"commit": false,
"tag": false
},
"commitUrlFormat": "https://github.com/owner/repo/commit/{{hash}}",
"compareUrlFormat": "https://github.com/owner/repo/compare/{{previousTag}}...{{currentTag}}"
}
```
### Usage
```bash
# First release
npx standard-version --first-release
# Regular release (auto-detect version bump)
npx standard-version
# Specific version bump
npx standard-version --release-as minor
npx standard-version --release-as 1.1.0
# Pre-release
npx standard-version --prerelease alpha
npx standard-version --prerelease beta
# Dry run
npx standard-version --dry-run
# Skip specific steps
npx standard-version --skip.changelog
```
### NPM Scripts
```json
// package.json
{
"scripts": {
"release": "standard-version",
"release:minor": "standard-version --release-as minor",
"release:major": "standard-version --release-as major",
"release:alpha": "standard-version --prerelease alpha",
"release:beta": "standard-version --prerelease beta",
"release:dry": "standard-version --dry-run"
}
}
```
## Changelog Generation
### conventional-changelog
```bash
# Install
npm install -g conventional-changelog-cli
# Generate changelog
conventional-changelog -p angular -i CHANGELOG.md -s
# Generate all history
conventional-changelog -p angular -i CHANGELOG.md -s -r 0
```
### git-cliff
```bash
# Install
cargo install git-cliff
# Generate changelog
git cliff -o CHANGELOG.md
```
```toml
# cliff.toml
[changelog]
header = "# Changelog\n\n"
body = """
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = true
commit_preprocessors = [
{ pattern = '\((\w+)\s#([0-9]+)\)', replace = "([#${2}](https://github.com/owner/repo/issues/${2}))" },
]
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactoring" },
{ message = "^style", group = "Styling" },
{ message = "^test", group = "Testing" },
{ message = "^chore", group = "Miscellaneous" },
]
filter_commits = true
tag_pattern = "v[0-9]*"
```
## Version Bumping Scripts
### Bash Script
```bash
#!/bin/bash
# bump-version.sh
CURRENT_VERSION=$(cat package.json | jq -r '.version')
echo "Current version: $CURRENT_VERSION"
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
case $1 in
major)
NEW_VERSION="$((MAJOR + 1)).0.0"
;;
minor)
NEW_VERSION="$MAJOR.$((MINOR + 1)).0"
;;
patch)
NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
;;
*)
echo "Usage: $0 {major|minor|patch}"
exit 1
;;
esac
echo "New version: $NEW_VERSION"
# Update package.json
npm version $NEW_VERSION --no-git-tag-version
# Create git tag
git add package.json package-lock.json
git commit -m "chore: bump version to $NEW_VERSION"
git tag -a "v$NEW_VERSION" -m "Version $NEW_VERSION"
```
### Python Script
```python
#!/usr/bin/env python3
# bump_version.py
import re
import sys
import subprocess
def get_current_version():
with open('setup.py', 'r') as f:
content = f.read()
match = re.search(r"version=['\"]([^'\"]+)['\"]", content)
return match.group(1) if match else None
def bump_version(current, bump_type):
major, minor, patch = map(int, current.split('.'))
if bump_type == 'major':
return f'{major + 1}.0.0'
elif bump_type == 'minor':
return f'{major}.{minor + 1}.0'
elif bump_type == 'patch':
return f'{major}.{minor}.{patch + 1}'
else:
raise ValueError(f'Invalid bump type: {bump_type}')
def update_version(old_version, new_version):
with open('setup.py', 'r') as f:
content = f.read()
content = content.replace(f"version='{old_version}'", f"version='{new_version}'")
with open('setup.py', 'w') as f:
f.write(content)
if __name__ == '__main__':
bump_type = sys.argv[1] if len(sys.argv) > 1 else 'patch'
current = get_current_version()
new = bump_version(current, bump_type)
print(f'Bumping version: {current}{new}')
update_version(current, new)
subprocess.run(['git', 'add', 'setup.py'])
subprocess.run(['git', 'commit', '-m', f'chore: bump version to {new}'])
subprocess.run(['git', 'tag', '-a', f'v{new}', '-m', f'Version {new}'])
```
## Multi-Package Versioning
### Lerna
```json
// lerna.json
{
"version": "independent",
"npmClient": "npm",
"command": {
"version": {
"conventionalCommits": true,
"message": "chore(release): publish"
},
"publish": {
"conventionalCommits": true
}
}
}
```
```bash
# Version all changed packages
npx lerna version
# Publish all changed packages
npx lerna publish
```
### Changesets
```bash
# Initialize
npx @changesets/cli init
# Add changeset
npx changeset add
# Version packages
npx changeset version
# Publish
npx changeset publish
```
## Common Issues
### Issue: No Version Bump
**Problem**: semantic-release not creating release
**Solution**: Check commit format, verify branch configuration
### Issue: Wrong Version Calculated
**Problem**: Major/minor/patch incorrectly determined
**Solution**: Review commit analyzer rules, check for missing prefixes
### Issue: Duplicate Tags
**Problem**: Tag already exists
**Solution**: Clean up tags, verify version wasn't already released
## Best Practices
- Use conventional commits consistently
- Automate version bumping in CI
- Generate changelogs automatically
- Tag releases in Git
- Use pre-release versions for testing
- Document breaking changes clearly
- Include migration guides for major versions
- Lock dependencies with exact versions
## Related Skills
- [git-workflow](../git-workflow/) - Branching strategies
- [github-actions](../../ci-cd/github-actions/) - CI automation
- [feature-flags](../feature-flags/) - Progressive rollout