Working with UpdateRequests
UpdateRequest CRDs are created by Headwind when a new image version is detected and approval is required. They represent pending updates awaiting manual approval.
Overview
An UpdateRequest is a Kubernetes Custom Resource that tracks:
- Which workload needs updating
- Current and new image versions
- Update policy that triggered it
- Approval status and who approved/rejected it
- Timestamps for all lifecycle events
Viewing UpdateRequests
Using kubectl
# List all UpdateRequests across all namespaces
kubectl get updaterequests -A
# Example output:
NAMESPACE NAME PHASE AGE
production nginx-update-v1-27-0 Pending 5m
staging redis-update-v7-2-0 Pending 2m
dev postgres-update-15-3 Completed 1h
# Get details of a specific UpdateRequest
kubectl get updaterequest nginx-update-v1-27-0 -n production -o yaml
# Watch for new UpdateRequests in real-time
kubectl get updaterequests -A --watch
# Filter by phase
kubectl get updaterequests -A -o json | jq '.items[] | select(.status.phase == "Pending")'
# Filter by workload kind
kubectl get updaterequests -A -o json | jq '.items[] | select(.spec.targetRef.kind == "Deployment")'
Using kubectl Plugin
# List all pending updates (formatted output)
kubectl headwind list
# Example output:
Namespace | Name | Deployment | Current Image | New Image | Phase
-----------|----------------------|------------------|---------------|--------------|--------
production | nginx-update-v1-27-0 | nginx-deployment | nginx:1.26.0 | nginx:1.27.0 | Pending
Using API
# List all UpdateRequests
curl http://headwind-api:8081/api/v1/updates
# Get specific UpdateRequest
curl http://headwind-api:8081/api/v1/updates/production/nginx-update-v1-27-0
UpdateRequest Structure
apiVersion: headwind.sh/v1alpha1
kind: UpdateRequest
metadata:
name: nginx-update-v1-27-0
namespace: production
creationTimestamp: "2025-11-06T10:00:00Z"
spec:
targetRef:
kind: Deployment # or StatefulSet, DaemonSet, HelmRelease
name: nginx-deployment
namespace: production
containerName: nginx # For image updates
currentImage: nginx:1.26.0 # For image updates
newImage: nginx:1.27.0 # For image updates
currentVersion: "1.26.0" # For HelmRelease updates
newVersion: "1.27.0" # For HelmRelease updates
policy: minor # Update policy that triggered this
status:
phase: Pending # Pending, Completed, Rejected, or Failed
createdAt: "2025-11-06T10:00:00Z"
lastUpdated: "2025-11-06T10:00:00Z"
# After approval/rejection:
approvedBy: "admin@example.com"
approvedAt: "2025-11-06T10:15:00Z"
# Or if rejected:
rejectedBy: "admin@example.com"
rejectedAt: "2025-11-06T10:15:00Z"
rejectionReason: "Not ready for production"
UpdateRequest Phases
| Phase | Description |
|---|---|
Pending | Waiting for approval |
Completed | Approved and successfully applied |
Rejected | Rejected by approver |
Failed | Approval granted but update failed to apply |
Approving Updates
Using kubectl Plugin (Recommended)
# Approve with explicit approver
kubectl headwind approve nginx-update-v1-27-0 -n production --approver admin@example.com
# Set default approver
export HEADWIND_APPROVER=admin@example.com
kubectl headwind approve nginx-update-v1-27-0 -n production
Using API
# Approve update
curl -X POST http://headwind-api:8081/api/v1/updates/production/nginx-update-v1-27-0/approve \
-H "Content-Type: application/json" \
-d '{"approver":"admin@example.com"}'
# Response:
{
"status": "approved",
"message": "Update approved and executed successfully",
"updateRequest": "nginx-update-v1-27-0"
}
What Happens on Approval
- Status Update: UpdateRequest phase changes to
Completed - Execution: Headwind immediately applies the update to the workload
- Tracking: Approver and approval timestamp recorded
- History: Update added to workload's update history annotation
- Notification: Slack/Teams/webhook notification sent
Rejecting Updates
Using kubectl Plugin
# Reject with reason
kubectl headwind reject nginx-update-v1-27-0 "Not ready for production" \
-n production --approver admin@example.com
# Reject without explicit reason
kubectl headwind reject nginx-update-v1-27-0 -n production
Using API
# Reject update
curl -X POST http://headwind-api:8081/api/v1/updates/production/nginx-update-v1-27-0/reject \
-H "Content-Type: application/json" \
-d '{
"approver": "admin@example.com",
"reason": "Not ready for production"
}'
# Response:
{
"status": "rejected",
"message": "Update rejected",
"updateRequest": "nginx-update-v1-27-0"
}
What Happens on Rejection
- Status Update: UpdateRequest phase changes to
Rejected - No Action: Workload remains unchanged
- Tracking: Rejector, timestamp, and reason recorded
- Notification: Notification sent with rejection reason
- Cleanup: UpdateRequest CRD remains for historical purposes
Filtering and Querying
By Namespace
# All updates in a namespace
kubectl get updaterequests -n production
# All pending updates in a namespace
kubectl get updaterequests -n production -o json | \
jq '.items[] | select(.status.phase == "Pending")'
By Workload Type
# All Deployment updates
kubectl get updaterequests -A -o json | \
jq '.items[] | select(.spec.targetRef.kind == "Deployment")'
# All HelmRelease updates
kubectl get updaterequests -A -o json | \
jq '.items[] | select(.spec.targetRef.kind == "HelmRelease")'
By Age
# Updates older than 1 hour
kubectl get updaterequests -A --sort-by=.metadata.creationTimestamp
# Updates from last hour using jq
kubectl get updaterequests -A -o json | \
jq --arg since "$(date -u -d '1 hour ago' '+%Y-%m-%dT%H:%M:%SZ')" \
'.items[] | select(.metadata.creationTimestamp > $since)'
By Specific Deployment
# Find all updates for a specific deployment
kubectl get updaterequests -n production -o json | \
jq '.items[] | select(.spec.targetRef.name == "nginx-deployment")'
Managing UpdateRequests
Deleting UpdateRequests
# Delete completed/rejected updates (cleanup)
kubectl delete updaterequest nginx-update-v1-27-0 -n production
# Bulk delete all completed updates
kubectl get updaterequests -A -o json | \
jq -r '.items[] | select(.status.phase == "Completed") | "\(.metadata.namespace) \(.metadata.name)"' | \
while read ns name; do kubectl delete updaterequest "$name" -n "$ns"; done
warning
Deleting a Pending UpdateRequest will prevent the update from being applied. Only delete UpdateRequests you're sure you want to cancel.
Auto-Cleanup
Headwind does not automatically delete UpdateRequests. They remain as historical records. You can set up a CronJob for cleanup:
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup-old-updaterequests
namespace: headwind-system
spec:
schedule: "0 0 * * *" # Daily at midnight
jobTemplate:
spec:
template:
spec:
serviceAccountName: headwind
containers:
- name: cleanup
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
# Delete completed/rejected updates older than 7 days
kubectl get updaterequests -A -o json | \
jq -r '.items[] | select(.status.phase == "Completed" or .status.phase == "Rejected") | select((.metadata.creationTimestamp | fromdateiso8601) < (now - (7 * 24 * 60 * 60))) | "\(.metadata.namespace) \(.metadata.name)"' | \
while read ns name; do
kubectl delete updaterequest "$name" -n "$ns"
done
restartPolicy: Never
Monitoring UpdateRequests
Prometheus Metrics
# Pending updates
headwind_updates_pending
# Approved updates (total)
headwind_updates_approved_total
# Rejected updates (total)
headwind_updates_rejected_total
# Successfully applied updates
headwind_updates_applied_total
# Failed update attempts
headwind_updates_failed_total
Alerting
Create alerts for stale UpdateRequests:
groups:
- name: headwind_updates
rules:
- alert: StaleUpdateRequests
expr: headwind_updates_pending > 10
for: 1h
annotations:
summary: "Many pending UpdateRequests"
description: "{{ $value }} UpdateRequests pending for over 1 hour"
- alert: HighUpdateFailureRate
expr: rate(headwind_updates_failed_total[5m]) > 0.1
for: 5m
annotations:
summary: "High update failure rate"
description: "Update failures detected"
Common Workflows
Bulk Approval
Approve all pending updates in a namespace:
kubectl get updaterequests -n staging -o json | \
jq -r '.items[] | select(.status.phase == "Pending") | .metadata.name' | \
while read name; do
kubectl headwind approve "$name" -n staging --approver ci-bot@example.com
done
Review Before Approval
# 1. List pending updates
kubectl headwind list
# 2. Get details of specific update
kubectl get updaterequest nginx-update-v1-27-0 -n production -o yaml
# 3. Check deployment history
kubectl headwind history nginx-deployment -n production
# 4. Make decision
kubectl headwind approve nginx-update-v1-27-0 -n production --approver admin@example.com
# OR
kubectl headwind reject nginx-update-v1-27-0 "Waiting for security scan" -n production
Automated Approval Based on Environment
#!/bin/bash
# Auto-approve staging, require manual approval for production
NAMESPACE=$1
UPDATE_REQUEST=$2
if [ "$NAMESPACE" == "staging" ] || [ "$NAMESPACE" == "dev" ]; then
echo "Auto-approving $UPDATE_REQUEST in $NAMESPACE"
kubectl headwind approve "$UPDATE_REQUEST" -n "$NAMESPACE" --approver auto-approve-bot@example.com
else
echo "Manual approval required for $UPDATE_REQUEST in $NAMESPACE"
# Send notification to Slack/Teams
fi
Troubleshooting
UpdateRequest Not Created
Check if Headwind detected the update:
# Check Headwind logs
kubectl logs -n headwind-system deployment/headwind | grep -i "update request"
# Verify deployment has annotations
kubectl get deployment nginx-deployment -n production -o jsonpath='{.metadata.annotations}'
# Check webhook/polling events
kubectl logs -n headwind-system deployment/headwind | grep -i webhook
Update Not Applied After Approval
Check UpdateRequest status:
kubectl get updaterequest nginx-update-v1-27-0 -n production -o yaml
# Look for status.phase: Failed
# Check status.message for error details
Check Headwind logs for errors:
kubectl logs -n headwind-system deployment/headwind | grep -i "execute update\|failed"
Stale Pending Updates
If updates remain Pending indefinitely:
- Check if they should be approved or rejected
- Consider implementing auto-cleanup
- Review approval workflow
Next Steps
- Approval Workflow - Configure approval process
- kubectl Plugin - Command-line tools
- API Reference - API documentation
- Rollback - Rollback failed updates