| Crates.io | headwind |
| lib.rs | headwind |
| version | 0.1.0 |
| created_at | 2025-11-10 16:20:36.576835+00 |
| updated_at | 2025-11-10 16:20:36.576835+00 |
| description | A Kubernetes operator to automate workload updates based on container image changes |
| homepage | |
| repository | https://github.com/b1tsized/headwind |
| max_upload_size | |
| id | 1925783 |
| size | 2,000,903 |
A Kubernetes operator for automating workload updates based on container image changes, written in Rust.
Headwind monitors container registries and automatically updates your Kubernetes workloads when new images are available, with intelligent semantic versioning policies and approval workflows.
Pull the latest release from GitHub Container Registry or Google Artifact Registry:
# From GitHub Container Registry (ghcr.io)
docker pull ghcr.io/headwind-sh/headwind:latest
# Or from Google Artifact Registry
docker pull us-docker.pkg.dev/secret-node-477601-s8/headwind/headwind:latest
# Apply Kubernetes manifests
kubectl apply -f deploy/k8s/namespace.yaml
kubectl apply -f deploy/k8s/crds/updaterequest.yaml
# Optional: Apply HelmRepository CRD if you want Helm chart auto-discovery
# (Skip if you already have Flux CD installed)
kubectl apply -f deploy/k8s/crds/helmrepository.yaml
kubectl apply -f deploy/k8s/rbac.yaml
# Update deployment to use the pulled image
kubectl apply -f deploy/k8s/deployment.yaml
kubectl apply -f deploy/k8s/service.yaml
Image Locations:
ghcr.io/headwind-sh/headwind:VERSIONus-docker.pkg.dev/secret-node-477601-s8/headwind/headwind:VERSIONAvailable Tags:
latest - Latest stable releaseX.Y.Z - Specific version (e.g., 0.1.0)X.Y - Latest patch version (e.g., 0.1)X - Latest minor version (e.g., 0)Image Details:
If you have Rust installed, you can install Headwind as a binary:
# Install from crates.io
cargo install headwind
# Run directly (requires KUBECONFIG)
headwind
Download pre-built binaries from GitHub Releases:
Platforms:
headwind-linux-amd64, headwind-linux-arm64headwind-darwin-amd64 (Intel), headwind-darwin-arm64 (Apple Silicon)headwind-windows-amd64.exe, headwind-windows-arm64.exe# Example: Download and install Linux binary
wget https://github.com/headwind-sh/headwind/releases/download/v0.1.0/headwind-linux-amd64
chmod +x headwind-linux-amd64
sudo mv headwind-linux-amd64 /usr/local/bin/headwind
# Run
headwind
# Clone the repository
git clone https://github.com/headwind-sh/headwind.git
cd headwind
# Build the Docker image
docker build -t headwind:latest .
# Load into your cluster (for kind/minikube/Docker Desktop)
kind load docker-image headwind:latest # or minikube image load headwind:latest
# Apply all Kubernetes manifests
kubectl apply -f deploy/k8s/namespace.yaml
kubectl apply -f deploy/k8s/crds/updaterequest.yaml
kubectl apply -f deploy/k8s/crds/helmrepository.yaml # Optional
kubectl apply -f deploy/k8s/rbac.yaml
kubectl apply -f deploy/k8s/deployment.yaml
kubectl apply -f deploy/k8s/service.yaml
Add annotations to your Deployments to enable Headwind:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
# Update policy: none, patch, minor, major, glob, force, all
headwind.sh/policy: "minor"
# Require approval before updating (default: true)
headwind.sh/require-approval: "true"
# Minimum time between updates in seconds (default: 300)
headwind.sh/min-update-interval: "300"
# Specific images to track (comma-separated, empty = all)
headwind.sh/images: "nginx, redis"
# Event source: webhook, polling, both, none (default: webhook)
headwind.sh/event-source: "webhook"
# Per-resource polling interval in seconds (overrides global HEADWIND_POLLING_INTERVAL)
# Only applies when event-source is "polling" or "both"
headwind.sh/polling-interval: "600"
# Automatic rollback on deployment failures (default: false)
headwind.sh/auto-rollback: "true"
# Rollback timeout in seconds (default: 300)
headwind.sh/rollback-timeout: "300"
# Health check retries before rollback (default: 3)
headwind.sh/health-check-retries: "3"
spec:
# ... rest of deployment spec
Headwind can monitor Flux HelmRelease resources and automatically discover new Helm chart versions from Helm repositories, updating based on semantic versioning policies.
Headwind requires the HelmRepository CRD to query Helm repositories for available chart versions:
If you have Flux CD installed: The CRD already exists - no action needed!
If you DON'T have Flux CD: Apply the HelmRepository CRD:
kubectl apply -f deploy/k8s/crds/helmrepository.yaml
Headwind supports both traditional HTTP Helm repositories and modern OCI registries (like ECR, GCR, ACR, Harbor, JFrog Artifactory, GitHub Container Registry, etc.).
HTTP Helm Repository:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: my-repo
namespace: default
spec:
url: https://charts.example.com # Traditional HTTP Helm repository
interval: 5m
type: default
# Optional: for private repositories
secretRef:
name: helm-repo-credentials # Secret with username/password keys
OCI Registry (ECR, GCR, ACR, Harbor, JFrog, GHCR, etc.):
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: my-oci-repo
namespace: default
spec:
url: oci://registry.example.com/helm-charts # OCI registry URL
interval: 5m
type: oci
# Optional: for private registries
secretRef:
name: oci-registry-credentials # Secret with username/password keys
Note: Headwind automatically detects whether to use HTTP or OCI based on the URL scheme (https:// vs oci://).
OCI Registry Support: Due to a limitation in the underlying oci-distribution Rust crate (v0.11), OCI Helm repositories may incorrectly query Docker Hub when the chart name matches a common Docker image name (e.g., busybox, nginx, redis, postgres). This results in discovering Docker container image tags instead of Helm chart versions.
Workaround: Use traditional HTTP Helm repositories (fully supported) or ensure your OCI Helm chart names don't conflict with popular Docker Hub image names. This limitation is expected to be resolved in future crate updates.
Status: HTTP Helm repositories work perfectly and are the recommended approach until this OCI limitation is addressed.
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: my-app
namespace: default
annotations:
# Update policy: none, patch, minor, major, glob, force, all
headwind.sh/policy: "minor"
# Require approval before updating (default: true)
headwind.sh/require-approval: "true"
# Minimum time between updates in seconds (default: 300)
headwind.sh/min-update-interval: "300"
# Event source: webhook, polling, both, none (default: webhook)
headwind.sh/event-source: "webhook"
# Per-resource polling interval in seconds (overrides global HEADWIND_POLLING_INTERVAL)
# Only applies when event-source is "polling" or "both"
headwind.sh/polling-interval: "600"
spec:
interval: 5m
chart:
spec:
chart: my-app
version: "1.2.3" # Headwind monitors this version
sourceRef:
kind: HelmRepository
name: my-repo
namespace: default
values:
# ... your values
How it works:
headwind.sh/policy annotationstatus.lastAttemptedRevision or spec.chart.spec.versionrequire-approval: "true" (default)require-approval: "false" (respects min-update-interval)Configuration:
Automatic version discovery is enabled by default. To disable:
# deploy/k8s/deployment.yaml
env:
- name: HEADWIND_HELM_AUTO_DISCOVERY
value: "false"
Private Helm Repositories:
For private repositories requiring authentication, create a Secret:
apiVersion: v1
kind: Secret
metadata:
name: helm-repo-credentials
namespace: default
type: Opaque
stringData:
username: myusername
password: mypassword
Metrics:
Helm-specific metrics are available at /metrics:
headwind_helm_releases_watched - Number of HelmReleases being monitoredheadwind_helm_chart_versions_checked_total - Version checks performedheadwind_helm_updates_found_total - Updates discoveredheadwind_helm_updates_approved_total - Updates approved by policyheadwind_helm_updates_rejected_total - Updates rejected by policyheadwind_helm_updates_applied_total - Updates successfully applied to HelmReleasesheadwind_helm_repository_queries_total - Repository index queries performedheadwind_helm_repository_errors_total - Repository query errorsheadwind_helm_repository_query_duration_seconds - Repository query durationheadwind.sh/pattern)Headwind supports two methods for detecting new images:
Event-driven updates are faster and more efficient. Configure your registry to send webhooks to Headwind.
Docker Hub:
Webhook URL: http://<headwind-webhook-service>/webhook/dockerhub
Generic Registry (Harbor, GitLab, GCR, etc.):
Webhook URL: http://<headwind-webhook-service>/webhook/registry
For external access, use an Ingress or LoadBalancer service.
If webhooks aren't available, enable registry polling:
# deploy/k8s/deployment.yaml
env:
- name: HEADWIND_POLLING_ENABLED
value: "true"
- name: HEADWIND_POLLING_INTERVAL
value: "300" # Poll every 5 minutes
When to use polling:
Note: Polling is less efficient and has a delay. Use webhooks when possible.
By default, all resources use webhooks as their event source (headwind.sh/event-source: "webhook"). You can override this on a per-resource basis:
Event Source Options:
webhook (default) - Only respond to webhook events, skip registry pollingpolling - Only use registry polling, ignore webhook eventsboth - Respond to both webhooks and polling (redundant but ensures coverage)none - Disable all update triggers for this resourceUse Cases:
Webhook-only resources (default):
metadata:
annotations:
headwind.sh/policy: "minor"
headwind.sh/event-source: "webhook" # Can be omitted (default)
Best for registries with webhook support. Updates are immediate when new images are pushed.
Polling-only resources:
metadata:
annotations:
headwind.sh/policy: "minor"
headwind.sh/event-source: "polling"
headwind.sh/polling-interval: "600" # Optional: poll every 10 minutes
Best for:
Both webhooks and polling:
metadata:
annotations:
headwind.sh/policy: "minor"
headwind.sh/event-source: "both"
Provides redundancy - updates will be detected via webhooks (fast) or polling (fallback).
Per-resource polling intervals:
When using event-source: "polling" or event-source: "both", you can override the global HEADWIND_POLLING_INTERVAL for specific resources:
metadata:
annotations:
headwind.sh/policy: "minor"
headwind.sh/event-source: "polling"
headwind.sh/polling-interval: "60" # Poll this resource every 60 seconds
This allows you to poll critical resources more frequently while checking less critical resources less often, reducing registry API load.
Example: Mixed event sources in a namespace:
# Production API - webhook-only (fastest)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-production
annotations:
headwind.sh/policy: "patch"
headwind.sh/event-source: "webhook"
---
# Staging API - polling every 5 minutes
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-staging
annotations:
headwind.sh/policy: "all"
headwind.sh/event-source: "polling"
headwind.sh/polling-interval: "300"
headwind.sh/require-approval: "false"
---
# Background job - polling every 30 minutes (low priority)
apiVersion: apps/v1
kind: Deployment
metadata:
name: background-job
annotations:
headwind.sh/policy: "minor"
headwind.sh/event-source: "polling"
headwind.sh/polling-interval: "1800"
Headwind creates UpdateRequest custom resources when it detects a new image version that matches a Deployment's policy. These CRDs track the approval workflow.
# List all UpdateRequests
kubectl get updaterequests -A
# Get details of a specific UpdateRequest
kubectl get updaterequest <name> -n <namespace> -o yaml
# Watch for new UpdateRequests in real-time
kubectl get updaterequests -A --watch
Each UpdateRequest has a phase indicating its current state:
apiVersion: headwind.sh/v1alpha1
kind: UpdateRequest
metadata:
name: nginx-update-1-26-0
namespace: default
spec:
targetRef:
kind: Deployment
name: nginx-example
namespace: default
containerName: nginx
currentImage: nginx:1.25.0
newImage: nginx:1.26.0
policy: minor
status:
phase: Pending
createdAt: "2025-11-06T01:00:00Z"
lastUpdated: "2025-11-06T01:00:00Z"
Headwind provides a modern web-based dashboard for viewing and managing update requests.
The Web UI is available on port 8082 by default:
# Port forward to access locally
kubectl port-forward -n headwind-system svc/headwind-ui 8082:8082
# Open in browser
open http://localhost:8082
The Web UI provides:
Access at http://localhost:8082 when port-forwarded, or expose via Service/Ingress for remote access.
The Web UI supports four authentication modes configured via the HEADWIND_UI_AUTH_MODE environment variable:
No authentication required. All actions are logged as "web-ui-user".
env:
- name: HEADWIND_UI_AUTH_MODE
value: "none"
Reads username from X-User HTTP header. Suitable for use behind an authenticating reverse proxy.
env:
- name: HEADWIND_UI_AUTH_MODE
value: "simple"
Example usage:
curl -H "X-User: alice" http://localhost:8082/
Validates bearer tokens using Kubernetes TokenReview API and extracts the authenticated username.
env:
- name: HEADWIND_UI_AUTH_MODE
value: "token"
Requirements:
authentication.k8s.io/tokenreviews (already included in deploy/k8s/rbac.yaml)Example usage:
# Get service account token
TOKEN=$(kubectl create token my-service-account -n default)
# Access Web UI with token
curl -H "Authorization: Bearer $TOKEN" http://localhost:8082/
Reads username from a configurable HTTP header set by an ingress controller or authentication proxy (e.g., oauth2-proxy, Authelia).
env:
- name: HEADWIND_UI_AUTH_MODE
value: "proxy"
- name: HEADWIND_UI_PROXY_HEADER # Optional, defaults to X-Forwarded-User
value: "X-Auth-Request-User"
All approval and rejection actions are logged with structured audit information:
{
"timestamp": "2025-11-08T23:00:00Z",
"username": "alice",
"action": "approve",
"resource_type": "Deployment",
"namespace": "default",
"name": "nginx-update-1-26-0",
"result": "success"
}
Audit logs use the dedicated log target headwind::audit and can be filtered with:
kubectl logs -n headwind-system deployment/headwind | grep headwind::audit
The dashboard automatically refreshes every 30 seconds to show the latest UpdateRequests. This can be disabled by clicking the "Auto-refresh" toggle in the UI.
The Web UI supports hot-reload configuration via ConfigMap. Changes to the ConfigMap are detected automatically without requiring pod restarts.
apiVersion: v1
kind: ConfigMap
metadata:
name: headwind-ui-config
namespace: headwind-system
data:
config.yaml: |
refresh_interval: 30
max_items_per_page: 20
Mount the ConfigMap in the deployment:
volumeMounts:
- name: ui-config
mountPath: /etc/headwind/ui
volumes:
- name: ui-config
configMap:
name: headwind-ui-config
The Web UI includes a comprehensive observability dashboard at /observability with real-time metrics visualization.
/metrics endpoint if no backend is availableConfigure metrics backend via ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: headwind-config
namespace: headwind-system
data:
config.yaml: |
observability:
metricsBackend: "auto" # auto | prometheus | victoriametrics | influxdb | live
prometheus:
enabled: true
url: "http://prometheus-server.monitoring.svc.cluster.local:80"
victoriametrics:
enabled: false
url: "http://victoria-metrics.monitoring.svc.cluster.local:8428"
influxdb:
enabled: false
url: "http://influxdb.monitoring.svc.cluster.local:8086"
org: "headwind" # InfluxDB v2 organization
bucket: "metrics" # InfluxDB v2 bucket
token: "your-api-token" # InfluxDB v2 API token
Backend Options:
auto - Automatically detects available backend (default)prometheus - Use Prometheus for metrics storage and queriesvictoriametrics - Use VictoriaMetrics (Prometheus-compatible API)influxdb - Use InfluxDB v2 for time-series datalive - Parse metrics directly from /metrics endpoint (no external backend)Auto-Discovery Priority: Prometheus → VictoriaMetrics → InfluxDB → Live
# Get current metrics from configured backend
curl http://localhost:8082/api/v1/metrics
# Get 24-hour time-series data for specific metric
curl http://localhost:8082/api/v1/metrics/timeseries/headwind_updates_pending
Deploy Prometheus to scrape Headwind metrics:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: monitoring
data:
prometheus.yml: |
scrape_configs:
- job_name: 'headwind'
static_configs:
- targets: ['headwind-metrics.headwind-system.svc.cluster.local:9090']
scrape_interval: 15s
The observability dashboard will automatically detect Prometheus and display metrics from it.
# List all pending updates across all namespaces
curl http://headwind-api:8081/api/v1/updates
# Get specific update by namespace and name
curl http://headwind-api:8081/api/v1/updates/{namespace}/{name}
# Approve update (automatically executes the update)
curl -X POST http://headwind-api:8081/api/v1/updates/{namespace}/{name}/approve \
-H "Content-Type: application/json" \
-d '{"approver":"user@example.com"}'
# Reject update with reason
curl -X POST http://headwind-api:8081/api/v1/updates/{namespace}/{name}/reject \
-H "Content-Type: application/json" \
-d '{"approver":"user@example.com","reason":"Not ready for production"}'
# Example: Approve an update
curl -X POST http://localhost:8081/api/v1/updates/default/nginx-update-1-26-0/approve \
-H "Content-Type: application/json" \
-d '{"approver":"admin@example.com"}'
Note: Approving an update immediately executes the deployment update and updates the UpdateRequest CRD status.
Headwind automatically tracks update history for all deployments and provides manual rollback capabilities.
# Install the kubectl plugin
sudo cp kubectl-headwind /usr/local/bin/
sudo chmod +x /usr/local/bin/kubectl-headwind
# Rollback a deployment
kubectl headwind rollback nginx-deployment -n production
# View update history
kubectl headwind history nginx-deployment -n production
# List all pending updates
kubectl headwind list
# Approve/reject updates
kubectl headwind approve nginx-update-v1-27-0 --approver admin@example.com
kubectl headwind reject nginx-update-v1-27-0 "Not ready" --approver admin@example.com
See KUBECTL_PLUGIN.md for complete plugin documentation.
# Get update history for a deployment
curl http://headwind-api:8081/api/v1/rollback/{namespace}/{deployment}/history
# Rollback to the previous image
curl -X POST http://headwind-api:8081/api/v1/rollback/{namespace}/{deployment}/{container}
# Example: Rollback nginx deployment
curl -X POST http://localhost:8081/api/v1/rollback/default/nginx-example/nginx
# Get history
curl http://localhost:8081/api/v1/rollback/default/nginx-example/history
When enabled, Headwind automatically monitors deployment health after updates and rolls back if failures are detected:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
# Enable automatic rollback (default: false)
headwind.sh/auto-rollback: "true"
# How long to monitor deployment health (default: 300s)
headwind.sh/rollback-timeout: "300"
# Number of failed health checks before rollback (default: 3)
headwind.sh/health-check-retries: "3"
Automatic rollback triggers on:
When a failure is detected, Headwind automatically:
All updates are tracked in deployment annotations:
# View update history in deployment annotations
kubectl get deployment my-app -o jsonpath='{.metadata.annotations.headwind\.sh/update-history}' | jq
# Example output:
[
{
"container": "app",
"image": "myapp:v1.2.0",
"timestamp": "2025-11-06T10:30:00Z",
"updateRequestName": "myapp-update-v1-2-0",
"approvedBy": "admin@example.com"
},
{
"container": "app",
"image": "myapp:v1.1.0",
"timestamp": "2025-11-05T14:20:00Z",
"updateRequestName": "myapp-update-v1-1-0",
"approvedBy": "webhook"
}
]
Headwind keeps the last 10 updates per container.
Headwind can send notifications about deployment updates to Slack, Microsoft Teams, or generic webhooks.
Configure notifications using environment variables in deploy/k8s/deployment.yaml:
env:
# Slack Configuration
- name: SLACK_ENABLED
value: "true"
- name: SLACK_WEBHOOK_URL
value: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
- name: SLACK_CHANNEL # Optional: override webhook default
value: "#deployments"
- name: SLACK_USERNAME # Optional: customize bot name
value: "Headwind Bot"
- name: SLACK_ICON_EMOJI # Optional: customize bot icon
value: ":rocket:"
# Microsoft Teams Configuration
- name: TEAMS_ENABLED
value: "true"
- name: TEAMS_WEBHOOK_URL
value: "https://outlook.office.com/webhook/YOUR-WEBHOOK-URL"
# Generic Webhook Configuration
- name: WEBHOOK_ENABLED
value: "true"
- name: WEBHOOK_URL
value: "https://your-webhook-endpoint.com/notifications"
- name: WEBHOOK_SECRET # Optional: HMAC signature verification
value: "your-secret-key"
- name: WEBHOOK_TIMEOUT # Optional: timeout in seconds (default: 10)
value: "10"
- name: WEBHOOK_MAX_RETRIES # Optional: max retries (default: 3)
value: "3"
# Dashboard Integration
- name: HEADWIND_UI_URL # Optional: adds "View in Dashboard" links to notifications
value: "https://headwind.example.com" # or http://localhost:8082 for local
Headwind sends notifications for the following events:
Slack notifications use Block Kit for rich formatting with:
HEADWIND_UI_URL is set)Teams notifications use Adaptive Cards with:
HEADWIND_UI_URL is set)Generic webhooks receive JSON payloads with HMAC SHA256 signature verification:
{
"event": "update_completed",
"timestamp": "2025-11-06T10:30:00Z",
"deployment": {
"name": "nginx",
"namespace": "production",
"currentImage": "nginx:1.25.0",
"newImage": "nginx:1.26.0",
"container": "nginx"
},
"policy": "minor",
"requiresApproval": true,
"updateRequestName": "nginx-update-1-26-0"
}
Signature is sent in the X-Headwind-Signature header as sha256=<hex>.
To verify:
import hmac
import hashlib
def verify_signature(secret, payload, signature):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature
Monitor notification delivery with Prometheus metrics:
headwind_notifications_sent_total - Total notifications sent successfullyheadwind_notifications_failed_total - Total notification failuresheadwind_notifications_slack_sent_total - Notifications sent to Slackheadwind_notifications_teams_sent_total - Notifications sent to Teamsheadwind_notifications_webhook_sent_total - Notifications sent via webhookPrometheus metrics available at:
http://headwind-metrics:9090/metrics
Available metrics:
headwind_webhook_events_total - Total webhook events receivedheadwind_webhook_events_processed - Successfully processed eventsheadwind_polling_cycles_total - Total polling cycles completedheadwind_polling_images_checked_total - Images checked during pollingheadwind_polling_new_tags_found_total - New tags discovered via pollingheadwind_polling_helm_charts_checked_total - Helm charts checked during pollingheadwind_polling_helm_new_versions_found_total - Helm chart versions discovered via pollingheadwind_polling_errors_total - Polling errors encounteredheadwind_updates_pending - Updates awaiting approvalheadwind_updates_approved_total - Total approved updatesheadwind_updates_rejected_total - Total rejected updatesheadwind_updates_applied_total - Successfully applied updatesheadwind_updates_failed_total - Failed update attemptsheadwind_updates_skipped_interval_total - Updates skipped due to minimum interval not elapsedheadwind_reconcile_duration_seconds - Controller reconciliation timeheadwind_deployments_watched - Number of watched Deploymentsheadwind_helm_releases_watched - Number of watched HelmReleasesheadwind_helm_chart_versions_checked_total - Helm chart version checks performedheadwind_helm_updates_found_total - Helm chart updates discoveredheadwind_helm_updates_approved_total - Helm chart updates approved by policyheadwind_helm_updates_rejected_total - Helm chart updates rejected by policyheadwind_helm_updates_applied_total - Helm chart updates successfully appliedheadwind_rollbacks_total - Total rollback operations performedheadwind_rollbacks_manual_total - Manual rollback operationsheadwind_rollbacks_automatic_total - Automatic rollback operationsheadwind_rollbacks_failed_total - Failed rollback operationsheadwind_deployment_health_checks_total - Deployment health checks performedheadwind_deployment_health_failures_total - Deployment health check failures detectedheadwind_notifications_sent_total - Total notifications sent successfullyheadwind_notifications_failed_total - Total notification failuresheadwind_notifications_slack_sent_total - Notifications sent to Slackheadwind_notifications_teams_sent_total - Notifications sent to Teamsheadwind_notifications_webhook_sent_total - Notifications sent via webhook┌─────────────────┐
│ Registry │
│ (Docker Hub, │
│ Harbor, etc) │
└────┬────────┬───┘
│ │
│Webhook │Polling
│ │(optional)
▼ ▼
┌──────────────────┐
│ Headwind │
│ ┌────────────┐ │
│ │ Webhook │ │◄─── Port 8080
│ │ Server │ │
│ └──────┬─────┘ │
│ │ │
│ ┌──────▼─────┐ │
│ │ Registry │ │
│ │ Poller │ │
│ └──────┬─────┘ │
│ │ │
│ ┌──────▼─────┐ │
│ │ Policy │ │
│ │ Engine │ │
│ └──────┬─────┘ │
│ │ │
│ ┌──────▼─────┐ │
│ │ Approval │ │◄─── Port 8081 (API)
│ │ System │ │
│ └──────┬─────┘ │
│ │ │
│ ┌──────▼─────┐ │
│ │Controller │ │
│ └──────┬─────┘ │
│ │ │
│ ┌──────▼─────┐ │
│ │ Metrics │ │◄─── Port 9090
│ └────────────┘ │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Kubernetes │
│ API Server │
└──────────────────┘
make build
# or
cargo build --release
# Run all tests (unit + integration)
make test
# or
cargo test
# Run only unit tests
cargo test --lib
# Run only integration tests
cargo test --test '*'
# Run specific integration test file
cargo test --test policy_integration_test
cargo test --test webhook_integration_test
# Run with output
cargo test -- --nocapture
The project includes both unit and integration tests:
Unit Tests (30 tests) - Located within source modules (src/)
cargo test --libIntegration Tests (40 tests) - Located in tests/ directory
tests/policy_integration_test.rs - Policy engine tests (12 tests)
tests/webhook_integration_test.rs - Webhook parsing tests (10 tests)
tests/rollback_integration_test.rs - Rollback functionality tests (18 tests)
Test Helpers - Located in tests/common/mod.rs
create_test_deployment() - Create Kubernetes Deployment fixturesheadwind_annotations() - Generate Headwind annotation setscreate_dockerhub_webhook_payload() - Docker Hub webhook JSONcreate_registry_webhook_payload() - OCI registry webhook JSON# Policy engine tests
cargo test should_update
# Webhook tests
cargo test webhook
# Test a specific policy type
cargo test patch_policy
cargo test minor_policy
cargo test glob_policy
# Test version handling
cargo test version_prefix
cargo test prerelease
Install all development tools:
make install
This installs:
cargo-audit - Security vulnerability scanningcargo-deny - Dependency license and security checkingcargo-udeps - Unused dependency detectioncargo-tarpaulin - Code coveragecargo-watch - Auto-rebuild on file changespre-commit - Git hooks for code qualityThe project uses pre-commit hooks to ensure code quality:
# Install hooks
pre-commit install
# Run manually
pre-commit run --all-files
# Hooks automatically run on git commit:
# - cargo fmt (formatting)
# - cargo clippy (linting)
# - cargo check (compilation)
# - YAML validation
# - Secret detection
# - Trailing whitespace removal
make run
# or
RUST_LOG=headwind=debug cargo run
Requires KUBECONFIG to be set and pointing to a valid Kubernetes cluster.
Headwind is currently in beta stage (v0.2.0-alpha). Core functionality is complete and tested:
Production readiness: Core workflow is functional. Suitable for testing environments. For production use, we recommend waiting for comprehensive integration tests and private registry support.
# Check logs
kubectl logs -n headwind-system deployment/headwind
# Common issues:
# 1. RBAC permissions - verify ServiceAccount has correct permissions
# 2. Cluster connectivity - ensure pod can reach Kubernetes API
# 3. Image pull - verify image is accessible
# Test webhook endpoint
kubectl port-forward -n headwind-system svc/headwind-webhook 8080:8080
curl -X POST http://localhost:8080/webhook/dockerhub \
-H "Content-Type: application/json" \
-d '{
"push_data": {"tag": "v1.2.3"},
"repository": {"repo_name": "myimage"}
}'
# Check webhook metrics
curl http://localhost:9090/metrics | grep webhook_events
Check the status in the approval API:
kubectl port-forward -n headwind-system svc/headwind-api 8081:8081
curl http://localhost:8081/api/v1/updates | jq
kubectl port-forward -n headwind-system svc/headwind-metrics 9090:9090
open http://localhost:9090/metrics
Or configure Prometheus to scrape:
- job_name: 'headwind'
kubernetes_sd_configs:
- role: pod
namespaces:
names:
- headwind-system
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+):(.+)
replacement: $1:9090
Use RBAC least-privilege
deploy/k8s/rbac.yamlSecure webhook endpoints
Protect approval API
Container security
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: headwind-network-policy
namespace: headwind-system
spec:
podSelector:
matchLabels:
app: headwind
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 8080 # Webhooks
- protocol: TCP
port: 8081 # API
- protocol: TCP
port: 9090 # Metrics
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443 # Kubernetes API
Q: How is this different from Argo CD or Flux?
A: Argo CD and Flux are GitOps tools that sync from Git. Headwind updates workloads when new container images are pushed to registries, regardless of Git state. They're complementary - you can use both.
Q: Can I use this with Flux/Argo?
A: Yes! Headwind can update the image tags, and Flux/Argo will see the change and sync. Or let Flux handle chart updates and Headwind handle image updates.
Q: Does this work with private registries?
A: Yes! Headwind reads credentials from your Kubernetes imagePullSecrets. Supports:
Simply configure your ServiceAccount's imagePullSecrets as usual, and Headwind will use them automatically.
Q: What about rollbacks?
A: Headwind includes both manual and automatic rollback support:
POST /api/v1/rollback/{namespace}/{deployment}/{container})headwind.sh/auto-rollback: "true" to automatically detect and rollback failed updateskubectl rollout undo for immediate rollbacksQ: Can I test updates in staging first?
A: Yes! Use different policies per namespace:
# staging namespace - auto-update all
headwind.sh/policy: "all"
headwind.sh/require-approval: "false"
# production namespace - require approval
headwind.sh/policy: "minor"
headwind.sh/require-approval: "true"
Q: What if I want to pin a specific version?
A: Use policy: "none" to prevent any updates, or remove Headwind annotations entirely.
Expected performance characteristics:
Tested with:
For larger scale, consider:
We welcome contributions! Please see:
# Fork and clone
git clone https://github.com/YOUR_USERNAME/headwind.git
cd headwind
# Build and test
cargo build
cargo test
# Run locally (requires k8s cluster)
export RUST_LOG=headwind=debug
cargo run
# Create a branch
git checkout -b feature/my-feature
# Make changes, commit, and push
git commit -m "feat: add new feature"
git push origin feature/my-feature
# Open a pull request
MIT License - see LICENSE file for details.