mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user